Java Spring Boot 基础与应用,其设计目的是用来简化新Spring应用的初始搭建以及开发过程
一个项目有多套前端目录配置
在原先statics
与templates
目录前面加一级目录,默认为default
,后面其他套前端页面根据需求增加比如default1
,default2
等,很容易因为开发工具出现问题导致不生效需要注意。最好重新maven clean,maven install一下
freemarker的路径修改,key修改配置文件,也可以修改Java配置文件,只能选择一种。我选择的是修改Java配置文件
1 |
|
修改静态文件的映射配置
1 |
|
还有一种是对resources文件进行改名的,不建议使用,这个是改maven配置文件
1 | <resources> |
多数据源配置
使用自定义数据源配置,取消默认数据源配置
通过切面对DRUID进行多数据源控制。还需要配置使用哪个配置,如果没有这么配置启动的时候会报循环引用错误org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
1 | sqlSessionFactory defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class] |
auto-configuration通过@EnableAutoConfiguration注解@SpringBootApplication包含该注解。@EnableAutoConfiguration注解开启了spring ApplicationContext的自动配置功能,它通过扫描classpath下的组件,满足不同Conditions的bean注册到容器中。当AutoConfiguration实现类打上@Configuration标签,可以作为spring配置类,当AutoConfiguration实现类打上@EnableConfigurationProperties标签,可以绑定自定义属性或者更多Conditional bean注册方法。在DataSourceAutoConfiguration中:
- DataSourceAutoConfiguration打上了@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })标签,这意味着只有当DataSource.class和EmbeddedDatabaseType.class出现在classpath时,DataSourceAutoConfiguration内的自动配置bean才可能被注册。
- DataSourceAutoConfiguration打上了@EnableConfigurationProperties(DataSourceProperties.class)标签,意味着配置文件中的属性和DataSourceProperties类自动绑定了。
配置文件中spring:datasource开头的属性将自动绑定到DataSourceProperties对象上,其他注解,如@ConditionalOnMissingBean, @ConditionalOnClass and @ConditionalOnProperty等,标识只要条件满足,bean definition将注册到ApplicationContext中。
spring boot 会默认加载org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
DataSourceAutoConfiguration类使用了@Configuration注解向spring注入了一个dataSource bean。可能是使用的时候又去创建了一个dataSource,dataSource创建,需要依赖DataSourceInitializerInvoker,DataSourceInitializerInvoker创建需要依赖dataSource发现dataSource正在创建,循环依赖。
1 | DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource, DataSourceProperties properties, |
找到这个DataSourceInitializerInvoker是什么时候注入到IOC容器中的,因此我们找到了DataSourceAutoConfiguration,继而找到了DataSourceInitializationConfiguration这个配置类
1 | /* |
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
。 不会执行里面代码,通过注解去掉DataSourceAutoConfiguration
,启动时直接初始化加载用户定义的数据源。
解决方法:
可以dataSource脱离Spring控制也可以启动的时候排除DataSourceAutoConfiguration:
1 |
|
JSON key 大写处理
处理Jason字符串中key为大写的数据,给实体类加上Json注解
1 |
|
表单验证
@Valid 注解
说明
注解 @Valid 的主要作用是用于数据效验,可以在定义的实体中的属性上,添加不同的注解来完成不同的校验规则
使用步骤
- Maven 引入相关依赖
- 自定义个异常类
1
2
3
4
5
6
7
8
9
10public class ParamErrorException extends RuntimeException {
public ParamErrorException() {
}
public ParamErrorException(String msg) {
super(msg);
}
} - 自定义响应枚举类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public enum ResultEnum {
SUCCESS(1000, "请求成功"), // 0
PARAMETER_ERROR(1001, "请求参数有误!"),
UNKNOWN_ERROR(9999, "未知的错误!");
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
} - 自定义响应对象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ResponseResult {
private Integer code;
private String msg;
public ResponseResult(){
}
public ResponseResult(ResultEnum resultEnum){
this.code = resultEnum.getCode();
this.msg = resultEnum.getMsg();
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
} - 实体类中添加 @Valid 相关注解
1
2
3
4
5
6
7
8
9
10
public class User {
private String username;
private String password;
private UserInfo userInfo;
} - Controller 中添加 @Valid 注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestController {
public ResponseResult findUserInfo( String username){
if (username == null || "".equals(username)) {
throw new ParamErrorException("username 不能为空");
}
return new ResponseResult(ResultEnum.SUCCESS);
}
public ResponseResult save( User user){
return new ResponseResult(ResultEnum.SUCCESS);
}
} - 全局异常处理类中处理 @Valid 抛出的异常
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
public class GlobalExceptionHandler {
/**
* 忽略参数异常处理器
*
* @param e 忽略参数异常
* @return ResponseResult
*/
public ResponseResult parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
log.error("", e);
return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "请求参数 " + e.getParameterName() + " 不能为空");
}
/**
* 缺少请求体异常处理器
*
* @param e 缺少请求体异常
* @return ResponseResult
*/
public ResponseResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
log.error("", e);
return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "参数体不能为空");
}
/**
* 参数效验异常处理器
*
* @param e 参数验证异常
* @return ResponseInfo
*/
public ResponseResult parameterExceptionHandler(MethodArgumentNotValidException e) {
log.error("", e);
// 获取异常信息
BindingResult exceptions = e.getBindingResult();
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
// 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
FieldError fieldError = (FieldError) errors.get(0);
return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
}
}
return new ResponseResult(ResultEnum.PARAMETER_ERROR);
}
/**
* 自定义参数错误异常处理器
*
* @param e 自定义参数
* @return ResponseInfo
*/
public ResponseResult paramExceptionHandler(ParamErrorException e) {
log.error("", e);
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
if (!StringUtils.isEmpty(e.getMessage())) {
return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), e.getMessage());
}
return new ResponseResult(ResultEnum.PARAMETER_ERROR);
}
} - 启动类
- 测试
可能出现问题
The valid characters are defined in RFC 7230 and RFC 3986
1)使用Tomcat7.0.69之前的版本
2)对url的特殊字符进行转义(推荐)。浏览器端处理encodeURI()
可以用于整个路径或者encodeURIComponent()
用于参数因为会把/也进行转码
3)修改tomcat配置文件
JSON parse error: Invalid UTF-8 start byte 0xa1; nested exception is com.fasterxml.jackson.core.JsonParseException: Invalid UTF-8 start byte 0xa1
Unrecognized token 'c': was expecting ('true', 'false' or 'null')
格式出错了
对于特殊字符前端需要进行转义,避免直接执行html,或者js脚本。
解决方法
- 避免自己写的过滤器
filter
xss html sql等过滤器导致编码错误。或者把utf-8编码头给截取掉了。或者过滤截取的时候把标准json格式截取掉了一部分导致错误 - yml加上运行使用使用转义字符,与对应配置
1
2
3
4
5
6
7
8spring:
profiles:
active: dev
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
parser:
allow_unquoted_control_chars: true1
2
3
4
5
6
7
8
9
10
public class JacksonConfig {
public ObjectMapper getObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper om = builder.build();
return om;
}
} - 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
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
{
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化
// Include.Include.ALWAYS 默认
// Include.NON_DEFAULT 属性为默认值不序列化
// Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的。这样对移动端会更省流量
// Include.NON_NULL 属性为NULL 不序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 允许出现特殊字符和转义符
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
// 允许出现单引号
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 字段保留,将null值转为""
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>()
{
public void serialize(Object o, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException
{
jsonGenerator.writeString("");
}
});
return objectMapper;
} - 测试demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Tmp {
private String id;
private String name;
}
public class Test {
public static void main(String args[]) {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_NULL_MAP_VALUES);
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.enable(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS);
try {
Tmp tmp = mapper.readValue("{\"tmp:id\":\"ab>c\",\"name\":null}", Tmp.class);
System.out.println(tmp.getId());
System.out.println(tmp.getName());
} catch (IOException e) {
e.printStackTrace();
}
}
}
错误位置代码:
1 | public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> { |
JSON解析报错
- 请求头与返回头:使用application/json(标准写法)而不是text/json(解析会报错)
应用
启动的时候加载字典信息到内存
1 |
|
@Autowired
@Autowired(required=true):当使用@Autowired注解的时候,其实默认就是@Autowired(required=true),表示注入的时候,该bean必须存在,否则就会注入失败
@Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错
在注入的过程中,扫描到公共方法中要注入的bean,并未找到,强行注入就会注入失败。我们又不能单独的去除改方法,所以我们采取的思想就是有bean就注入,没有就不注入。解决办法就是@Autowired(required=false)。
容器的启动顺序:
先加载父容器(spring),后加载子容器(springmvc)。所以在Controller里面注入service时,父容器中的bean已经初始化完毕,所以正常注入。
在父子容器中,父容器元素对子容器可见,子容器对父容器的元素不可见。所以父容器中不能获取子容器的元素,但是子容器可以获取父容器的元素。
当前容器均可获取当前容器中的元素,也就是说在service中可以注入其他service。但是,当前容器不可以注入自己。