GeXiangDong

精通Java、SQL、Spring的拼写,擅长Linux、Windows的开关机

0%

仅仅Autowired注解

@Autowired在查找构件时,会从构件库中查找所有构件,查找所有是被注解变量的类型或该类型的子类。

  1. 如果找到了且只找到了一个,那么就给被注解变量设置成此构件。
  2. 如果一个也没找到,看Autowired注解的required参数的值,如果是true(默认值)则失败,终止spring环境的启动;如果是false则给被注解变量赋值为null。
  3. 如果找到了多个:
    1. 如有在找到的多个中有且仅有一个构件被标注了@Primary,则使用这个。
    2. 如果@Primary有多个或者一个也没有,终止spring环境的启动,抛出异常。

Autowired注解和Qualifier注解一起使用

在使用@Autowired注解装载构件时,可以增加一个@Qualifier指定一个待装载构件的名字

1
@Autowired @Qualifier("thisone") MyObject myObject;

上面这种写法可以解决多个MyObject的子类被设置成component的状况
相应的,在构件上有两种写法:

1
2
3
@Service("thisone")
public MyObject{
}

也可以

1
2
3
4
@Qualifier("thisone")
@Service
public MyObject{
}

注意给构件起名不要重复,重名会抛异常的。

定制RestController中Date类型转换为JSON时的格式有两种方法:

  • 通过application.yml配置修改全局设置,对所有Date型都生效
  • 给需要指定的属性增加注解,仅对被注解的属性生效

两个方法都有,注解优先级高。

application.yml中可这样设置

1
2
3
4
5
6
spring:
jackson:
date-format: yyyy-MM-dd #用字符串表示日期时间时的字符串格式
time-zone: GMT+8 #设置时区,不设置用格林威治时间
serialization:
write-dates-as-timestamps: false #值为true表示用long型时间戳

用注解设置可以使用下面2个注解

long型的timestamp表示日期:

1
@JsonFormat(shape = JsonFormat.Shape.NUMBER)

字符串表示日期:

1
@JsonFormat(timezone="GMT+8", pattern="yyyy-MM-dd")

Mybatis写mapper时,每个表都需要INSERT UPDATE写很长的语句比较麻烦,80%的表和java bean是简单的一一对应关系,因此考虑使用mybatis的SQLProvider注解减少这部分重复劳动。
SELECT和DELETE语句由于涉及字段较少,写起来还在可接收范围内,所以没做通用的。

也可以把javabean增加写注解,类似JPA那样指定表名、字段名、主键等,但做得太多就失去了mybatis依赖SQL的灵活性了

InsertUpdateSqlProvider.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.jdbc.SQL;


/**
* 使用此类有几个默认规则: 表名和类名相对应,属性名和字段名对应
* java类内用驼峰命名法;数据库表和字段都用下划线
* 类的属性定义的变量名和get/set方法名,以及set的参数类型一致,才会映射到数据库字段
* 例如: private String name; 而且有 public void setName(String name) 和 public String getName()方法
* 如果不是严格按照此规则定义的属性,不会被影射到数据库字段上
*/
public class InsertUpdateSqlProvider {

/**
* id如果传入了值,会被insert使用;如果id为null,不会被insert的columns列出
*/
public static String insert(Object obj) {
Map<String, String> map;
try {
map = getFieldsMap(obj, true);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return new SQL() {
{
INSERT_INTO(getTableName(obj));
for (String col : map.keySet()) {
VALUES(col, map.get(col));
}
}
}.toString();
}

private static String updateById(Object obj, boolean includeNullValueField) {
Map<String, String> map;
try {
map = getFieldsMap(obj, includeNullValueField);
map.remove("id");
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return new SQL() {
{
UPDATE(getTableName(obj));
for (String col : map.keySet()) {
SET(col + "=" + map.get(col));
}
WHERE("id = #{id}");
}
}.toString();
}

public static String updateById(Object obj) {
return updateById(obj, true);
}

public static String updateNonNullById(Object obj) {
return updateById(obj, false);
}

private static Map<String, String> getFieldsMap(Object obj, boolean includeNullValue)
throws IllegalArgumentException, IllegalAccessException {
HashMap<String, String> result = new HashMap<>();
Class<?> cls = obj.getClass();
Field[] fields = cls.getDeclaredFields(); // getDeclaredFields
for (Field f : fields) {
String col = f.getName();
String colName = col.substring(0, 1).toUpperCase() + col.substring(1);
f.setAccessible(true);
try {
cls.getMethod("get" + colName);
cls.getMethod("set" + colName, f.getType());
} catch (NoSuchMethodException | SecurityException e) {
continue;
}
if ((!"id".equals(col) && includeNullValue) || f.get(obj) != null) {
result.put(camelCase2Underscore(col), "#{" + col + "}");
}
}
return result;
}

public static String getTableName(Object obj) {
return camelCase2Underscore(obj.getClass().getSimpleName());
}

public static String camelCase2Underscore(String s) {
StringBuffer buf = new StringBuffer();
for (String w : s.split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")) {
buf.append("_").append(w.toLowerCase());
}
return buf.substring(1);
}
}

使用通用provider的Mapper类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.UpdateProvider;
import org.springframework.stereotype.Repository;

@Repository
public interface GenericDao {

@UpdateProvider(type=InsertUpdateSqlProvider.class, method="updateById")
public int updateById(Object bean);

@UpdateProvider(type=InsertUpdateSqlProvider.class, method="updateNonNullById")
public int updateNonNullById(Object bean);

@Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
@InsertProvider(type=InsertUpdateSqlProvider.class, method="insert")
public int insert(Object bean);

@InsertProvider(type=InsertUpdateSqlProvider.class, method="insert")
public int insertWithoutGeneratedKey(Object bean);
}

POM文件中加入spring-boot-devtoools的依赖,可以在修改后自动重启,方便开发过程中测试。

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

需要IDE工具打开自动编译,因为devtools是监视class文件的修改后自动重启,不是监视src目录下的java文件。

IntelliJ IDEA可以通过Build菜单下的Build Project来编译项目。

如果需要配置devtools,可以通过在application.yml里加入

1
2
3
4
5
6
7
spring:
devtools:
restart:
enabled: true
livereload:
port: 35727 # 默认是35729,如果开发环境中运行多个项目都用devtools,这个端口会冲突,需要改成不同的
# 还有其他一些属性可配置,请参考文档

如果使用了maven的多模块功能,devtools的依赖需要加到子模块中,加入到父模块不起作用,因为optional=true,是可选依赖,子模块不会继承它。

CURL常用用法汇总。

GET 方法

调用GET方法最简单

1
curl  http://127.0.0.1:8080/tvseries/

增加 header user-agent

1
curl -H "user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0“ http://localhost:8080

增加header 授权信息

1
curl -H "Authorization: Bearer 7e161a1a-4590-4696-9d90-516c54113906" http://localhost:8080/api/exam

显示详细信息 -v

增加 -v 参数可以显示连接过程的详细信息,发送和接收到的HTTP头信息等等,如果不增加 -v,只显示 http response body部分内容

1
curl -v http://localhost:8080

POST 方法

1
curl -H "Content-Type:application/json" -X POST --data '{"name": "West World", “originRelease":"2016-10-02"}’ http://127.0.0.1:8080/tvseries/

上面的data参数是用的单引号包含起来的,内容json部分有双引号。这种写法在windows下可能会出错,需要用双引号,改成如下方式:

1
curl -H "Content-Type:application/json" -X POST --data "{\"name\": \"West World\", \“originRelease\":\"2016-10-02\"}\" http://127.0.0.1:8080/tvseries/

DELETE 方法

1
curl -X DELETE https://127.0.0.1:8080/tvseries/23/

PUT 方法

1
curl  -H “Content-Type:application/json” -X PUT —data ‘{“name”: “Person of Interest”} http://127.0.0.1:8080/tvseries/33/

PUT 方法同 POST 方法,一般需要指定传输的数据。

提交压缩格式的post body

需要服务端支持才可,并不是标准的HTTP服务。

使用临时文件,需要2个命令,先创建一个压缩文件,后发送:

1
2
3
4
echo '{ "mydummy" : "json" }' | gzip > body.gz

curl -v -i http://127.0.0.1:8080/ss -H 'Content-Encoding: gzip' -H 'Content-Type:application/json' --data-binary @body.gz

或者使用管道符合并到一行上

1
2
echo '{"type": "json", "length": 2222}' | gzip | curl -H "Content-Type: application/json" -H "Content-Type: gzip" -X POST -d @- http://127.0.0.1:8080/ss

在使用spring的时候,如果Controller中抛出异常,会被spring显示在客户端界面,而日志中一般没有记录。
客户端对异常的显示也是经spring处理后的信息,没有堆栈,这不方便找错和改错。

可以通过RestControllerAdvice注解定义一个异常处理类来解决这个问题。代码如下

注:也可以不继承ResponseEntityExceptionHandler类,此处继承只是省了一些通用异常的处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;


/**
*
* RestController执行过程中发生异常会被此处捕获处理
*
*/
@RestControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
private static final Log log = LogFactory.getLog(ControllerExceptionHandler.class);

/**
* 通过ExceptionHandler来设置待捕获的异常,Throwable可捕获任何异常,但优先级最低,因此
* HttpRequestMethodNotSupportedException HttpMediaTypeNotSupportedException
* HttpMediaTypeNotAcceptableException MissingPathVariableException
* MissingServletRequestParameterException ServletRequestBindingException
* ConversionNotSupportedException TypeMismatchException
* HttpMessageNotReadableException HttpMessageNotWritableException
* MethodArgumentNotValidException MissingServletRequestPartException
* BindException NoHandlerFoundException AsyncRequestTimeoutException
* 等已经在父类声明捕获的异常不会被此方法处理。
*/
@ExceptionHandler(Throwable.class)
@ResponseBody
ResponseEntity<Object> handleControllerException(Throwable ex, WebRequest request) {
Map<String,String> responseBody = new HashMap<>();
// 这里控制返回给客户端的信息
responseBody.put("message","internal server error. " + ex.getMessage());

Exception e;
if(ex instanceof Exception) {
e = (Exception) ex;
}else {
e = new Exception(ex);
}
return handleExceptionInternal(e, responseBody, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}

/**
* 需要覆盖这个方法,并且在此方法里记录日志;查看ResponseEntityExceptionHandler源码可知,
* 有些异常被父类捕获,不会进入此类的handleControllerException,因此如果在handleControllerException
* 记录异常日志,会导致部分异常无日志
*/
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body,
HttpHeaders headers, HttpStatus status, WebRequest request) {
if(log.isErrorEnabled()) {
log.error("内部错误", ex);
}
return super.handleExceptionInternal(ex, body, headers, status, request);
}
}

查看java内存使用状况可以通过jmap命令,例如: jmap -histo:live PID

如果执行命令是出现下述错误:

jmap -histo:live 19114
19114: Unable to open socket file: target process not responding or HotSpot VM not loaded
The -F option can be used when the target process is not responding

这是因为19114号进程的属主和当前执行jmap的用户不一致,换成一致的用户即可,例如:

1
sudo -u tomcatuser jmap -histo:live 19114

可以把方法设置成返回 ResponseEntity<T> 类型,其中的泛型T取代以前想返回的类型,然后通过 ResponseEntity.status(HttpStatus.OK).body(result) 方法创建返回结果,其中第一个status就是设置返回的ResponseCode

1
2
3
4
5
6
7
8
9
10
11
12
@PostMapping
public ResponseEntity<Map<String, Object>> login(){
HashMap<String, Object> result = new HashMap<>();
boolean isOk = false;
if( !isOk ) {
result.put("message", "invalid username or password");
// status(HttpStatus.BAD_REQUEST) 是设置返回的状态码
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
}else {
return ResponseEntity.status(HttpStatus.OK).body(result);
}
}

java8中提供了类似javascript Promise方式的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import java.util.concurrent.*;
import java.util.*;
import java.util.stream.*;

public class TestPromise{

public static void main(String[] argvs) throws Exception{
// 简单的lambda表达式的例子
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
System.out.println("-------");
list.forEach(System.out::println);
System.out.println("-------");

//下面是2个异步执行,执行完毕后使用结果的例子
String s = CompletableFuture.supplyAsync(() -> {
System.out.println("enter s1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s1");
return "hello";
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println("enter s2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s2");
return "world";
}), (s1, s2) -> {return s1 + " " + s2;} ).join();
System.out.println(s);
System.out.println("-------");

// 多个方法异步执行,结果通过操纵一个共有的Map来保存,待全部执行完毕后显示结果
Map<String, String> result = new HashMap<String, String>();
CompletableFuture.allOf(
CompletableFuture.supplyAsync(() -> {
System.out.println("enter s1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s1");
result.put("k1", "11111");
return "11";
}),
CompletableFuture.supplyAsync(() -> {
System.out.println("enter s2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s2");
result.put("k2", "22222");
return "22";
}),
CompletableFuture.supplyAsync(() -> {
System.out.println("enter s3");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s3");
result.put("k3", "33333");
return "33";
})
).get();
//.get()方法会等待allOf中所有参数方法执行完毕
System.out.println(result.get("k1") + " " + result.get("k2") + " " + result.get("k3"));


System.out.println("\r\n-------------------\r\n");
// 改进一下第一个方法,使其更易读;
CompletableFuture<Map<String, Object>> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println("enter s1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s1");
Map<String, Object> map = new HashMap<String, Object>();
map.put("s1", "11111");
return map;
});
CompletableFuture<Map<String, Object>> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println("enter s2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s2");
Map<String, Object> map = new HashMap<String, Object>();
map.put("s2", "22222");
return map;
});
CompletableFuture<Map<String, Object>> cf3 = CompletableFuture.supplyAsync(() -> {
System.out.println("enter s3");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("s3", "33333");
return map;
});
// 注释掉的这2行和上一个例子相同,不处理cf1,cf2,cf3的返回值
// CompletableFuture<Void> cf = CompletableFuture.allOf(cf1, cf2, cf3);
// cf.get();
// 把各个CompletableFuture的执行结果放到一起

// 把结果合并成一个List
//List<Map<String, Object>> resultList = Stream.of(cf1, cf2, cf3).map(CompletableFuture::join).collect(Collectors.toList());
//resultList.forEach(n -> System.out.println("\t---" + n));

// 把结果合并到一个map里
Map<String, Object> resultMap = new HashMap<String, Object>();
Stream.of(cf1, cf2, cf3).map(CompletableFuture::join).forEach(v -> resultMap.putAll(v));
System.out.println(resultMap);
}
}

参考:

CompletableFurure 可参考javadoc https://docs.oracle.com/javase/8/docs/api/index.html?java/util/concurrent/CompletableFuture.html

java的Lambda表达式介绍 http://blog.oneapm.com/apm-tech/226.html

http://www.baeldung.com/java-completablefuture

javax.net.ssl.SSLHandshakeException

自制ssl证书,报错:javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: Certificate signature algorithm disabled

原因是jre里信任的CA不包含自制CA,可以用keytools加入自制ca:

1
keytool -import -noprompt -trustcacerts -alias YOURALIS -file 自制CA的PEM文件 -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit

changeit 是默认密码

查看所有信任的CA可以用命令:

1
keytool -list  -keystore %JAVA_HOME%/jre/lib/security/cacerts

java.security.cert.CertPathValidatorException

在高版本jdk,访问某些老网站时,可能出错:java.security.cert.CertPathValidatorException: Usage constraint TLSServer check failed: SHA1 used with certificate: CN=GeoTrust Global CA, O=GeoTrust Inc., C=US. Usage was tls server

这是由于网站使用了SHA1的签名证书,而此版本JDK已经把SHA1证书列为禁止导致,如果要打开支持,可以编辑 %JAVA_HOME%/jre/lib/security/java.security文件,找到jdk.certpath.disabledAlgorithms=这行(后面值内容可能不同):

jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA &amp; usage TLSServer, \
RSA keySize &lt; 1024, DSA keySize &lt; 1024, EC keySize &lt; 224

把里面SHA1部分删除掉改成

jdk.certpath.disabledAlgorithms=MD2, MD5, RSA keySize &lt; 1024, \
DSA keySize &lt; 1024, EC keySize &lt; 224

另外jdk.tls.disabledAlgorithms 也是类似禁止某些tls算法的。