记录 Spring Boot 学习过程
Maven 打包注意事项
- 配置文件变量替换
- 在
SpringBoot
中使用Maven
的resource-filter
时 配置文件内的变量应该 由${project.version}
改为@project.version@
- 在
读取配置
- 在 Bean 上添加注解
@Component
@ConfigurationProperties(prefix = "sms")
- 在需要使用的地方添加@Autowired即可
- 配置部分
application.yml
1
2
3
4
5
6
7
8sms:
regionId: 'cn-hangzhou'
accessKeyId: '****************'
secret: '****************'
endpointName: 'cn-hangzhou'
product: 'Sms'
domain: 'sms.aliyuncs.com'
sign: 'XX科技' - 代码部分
SmsSetting.java
1
2
3
4
5
6
7
8
9
10
11
public class SmsSetting {
private String regionId;
private String accessKeyId;
private String secret;
private String endpointName;
private String product;
private String domain;
private String sign;
}
读取自定义文件配置
- 在 Bean 上添加注解
@Component
@ConfigurationProperties(prefix = "git.commit", exceptionIfInvalid = false)
@PropertySource("classpath:git.properties")
@PropertySource
配置的是文件路径- 配置文件部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#Generated by Git-Commit-Id-Plugin
#Fri Sep 15 14:03:50 CST 2017
release =
SDWM--20090101U =
2017-09-15T14\:03\:50+0800 =
admin@yumc.pw =
502647092 =
1.5.0 =
=
=
45066a3aba4a698f99e236ac2b90843a97045467 =
45066a3 =
45066a3-dirty =
45066a3-dirty =
feat\: \u610F\u89C1\u53CD\u9988\u6DFB\u52A0\u9A8C\u8BC1\u7801 =
feat\: \u610F\u89C1\u53CD\u9988\u6DFB\u52A0\u9A8C\u8BC1\u7801 =
2017-09-15T09\:47\:01+0800 =
admin@yumc.pw =
502647092 =
true =
http\://192.168.0.18\:3000/DesignPlatform/DesignPlatformService =
= - 代码部分
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
public class GitInfo {
private Id id;
private Message message;
private User user;
public static class Id {
String abbrev;
String describe;
String describeShort;
}
public static class Message {
private String full;
// 由于 short 是Java关键词 所以这里改为大写
// Spring 可以识别 标准驼峰 横杠 和 全大写
private String Short;
}
public static class User {
private String email;
private String name;
}
}
参数验证
- 需要验证的 Entity 字段添加验证注解 @NotEmpty 还有更多的注解 详见 验证注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Demand {
private Integer id;
private String title;
private Integer styleId;
private String styleName;
private BigDecimal budget;
private Integer budgetStatus;
private Short auditStatus;
private Integer classId;
} - 需要验证的参数添加 @Valid 注解 随后 紧跟 一个 BindingResult 用于处理错误
必须在验证的参数后添加一个 BindingResult 不然 Spring 会直接返回 400 错误
1
2
3
4
5public String add( DemandForm demandForm, BindingResult result){
if(result.hasErrors()){
return "验证错误: " + result.getAllErrors().get(0).getDefaultMessage()
}
}
验证注解
@org.hibernate.validator.constraints.Email
- 限制必须为一个邮箱
@org.hibernate.validator.constraints.Length(value)
- 限制必须为一个指定value长度的字符串
@org.hibernate.validator.constraints.NotBlank
- 限制必须为一个不为空的字符串
@org.hibernate.validator.constraints.NotEmpty
- 限制必须为一个不为空的集合
@org.hibernate.validator.constraints.Range(min, max)
- 限制必须为一个字符串或数字 在指定的范围内
@org.hibernate.validator.constraints.SafeHtml
- 限制必须为一个安全的HTML
@org.hibernate.validator.constraints.ScriptAssert(script, lang)
当前注解标记于Class上
- 使用指定的脚本引擎验证当前对象的数据
_this
代表当前类1
2
3
4
5
public class CalendarEvent {
private Date startDate;
private Date endDate;
}
@org.hibernate.validator.constraints.URL
- 限制必须为一个URL地址
@javax.validation.constraints.DecimalMax(value)
- 限制必须为一个不大于value的数字
@javax.validation.constraints.DecimalMin(value)
- 限制必须为一个不小于value的数字
@javax.validation.constraints.Digits(integer, fraction)
- 限制必须为一个小数 且整数部分的位数不能超过integer 小数部分的位数不能超过fraction
@javax.validation.constraints.Future
- 限制必须是一个将来的日期
@javax.validation.constraints.Past
- 限制必须是一个过去的日期
@javax.validation.constraints.Max(value)
- 限制必须为一个不大于value的数字
@javax.validation.constraints.Min(value)
- 限制必须为一个不小于value的数字
@javax.validation.constraints.NotNull
- 限制必须不为Null
@javax.validation.constraints.Null
- 限制只能为Null
@javax.validation.constraints.Pattern(regexp = "^(.*)$")
- 限制必须符合指定的正则表达式
@javax.validation.constraints.Size(max, min)
- 限制字符长度必须在min到max之间
参数解析
SpringBoot自带的参数解析
- 创建一个
BeanKit
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
32package com.sixi.ktpd.kits;
import ...;
public class BeanKit implements ApplicationContextAware {
private static ApplicationContext context;
private BeanKit() {}
public static ApplicationContext getApplicationContext() {
return context;
}
public static <T> T get(String name) {
return (T) context.getBean(name);
}
public static <T> T getOfType(Class<T> clazz) {
return context.getBean(clazz);
}
public static <T> Collection<T> getListOfType(Class<T> clazz) {
return context.getBeansOfType(clazz).values();
}
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
} - 在继承
WebMvcConfigurerAdapter
的配置文件内重写这个方法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
26package pw.yumc.demo.config;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/5/27 13:50.
*/
public class Configurer extends WebMvcConfigurerAdapter {
/**
* 自动扫描相关组件并且注册
*/
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : BeanKit.getListOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : BeanKit.getListOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : BeanKit.getListOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
}
自定义参数解析
- 由于需求中经常用到JSON传递数据 每次都需要手动解析一次
1
2
3public Map submit(HttpServletRequest request){
JSON.parseObject(request.getParameter("json"))
} - Spring 支持自定义参数解析的 首先 创建一个类 实现
org.springframework.web.method.support.HandlerMethodArgumentResolver
接口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
28package pw.yumc.demo.resolver.argument;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/6/8 13:50.
*/
public class JSONArgumentResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return JSONObject.class.isAssignableFrom(paramType) || JSONArray.class.isAssignableFrom(paramType);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)throws Exception {
Class<?> paramType = parameter.getParameterType();
String json = webRequest.getParameter(parameter.getParameterName());
if (JSONObject.class.isAssignableFrom(paramType)) {
return JSON.parseObject(json);
} else if (JSONArray.class.isAssignableFrom(paramType)) {
return JSON.parseArray(json);
} else {
Method method = parameter.getMethod();
throw new UnsupportedOperationException("Unknown parameter type: " + paramType + " in method: " + method);
}
}
} - 在
Spring-Boot
配置类里添加这个解析器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package pw.yumc.demo.config;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/5/27 13:50.
*/
public class Configurer extends WebMvcConfigurerAdapter {
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new JSONArgumentResolver());
}
} - 最后在方法上就可以直接使用了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package pw.yumc.demo.config;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/5/27 13:50.
*/
public class Controller {
public Map submit(JSONObject json){
}
}
转换 Json 到实体类
- 在SpringBoot中配置消息转换器即可转换Json为实体类
- 创建一个配置文件继承于 WebMvcConfigurerAdapter (如果有直接覆盖方法即可)
- WebMvcConfig.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
33package pw.yumc.spring.core.config;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/12/20 16:01.
*/
public class CoreConfig extends WebMvcConfigurerAdapter {
/**
* 消息类型转换
*/
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
//1.需要定义一个convert转换消息的对象;
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
//2:添加fastJson的配置信息;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
////3处理中文乱码问题
//List<MediaType> fastMediaTypes = new ArrayList<>();
//fastMediaTypes.add(MediaType.APPLICATION_JSON);
//fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
//fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
//4.在convert中添加配置信息.
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
converters.add(fastJsonHttpMessageConverter);
}
}
- WebMvcConfig.java
- 创建两个简单的实体类
- Order.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package pw.yumc.web.open.domain.entity;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/12/20 14:40.
*/
public class Order {
private Integer id;
private Integer userId;
private BigDecimal amount;
List<Product> products;
} - Product.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package pw.yumc.web.open.domain.entity;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/12/20 14:41.
*/
public class Product {
private Integer id;
private String name;
private BigDecimal price;
}
- Order.java
- 创建一个测试控制器
- TestController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package pw.yumc.web.open.controller;
import ...;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/12/20 10:42.
*/
public class TestController {
public Order order( Order order){
return order;
}
}
- TestController.java
- 使用CURL测试请求
- 测试命令
1
2
3curl -sX POST http://127.0.0.1:8080/test/order \
-H 'Content-Type: application/json' \
-d '{ "id":111,"userId":1,"amount":100,"products":[{"id":123,"name":"testproduct","price":23}]}' - 测试结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15$ curl -sX POST http://172.30.34.3:1221/test/order \
> -H 'Content-Type: application/json' \
> -d '{"id":111,"userId":1,"amount":100,"products":[{"id":123,"name":"testproduct","price":23}]}'
{
"amount":100,
"id":111,
"products":[
{
"id":123,
"name":"testproduct",
"price":23
}
],
"userId":1
}异常处理
- 测试命令
全局处理异常
- 新建一个类
GlobeExceptionHandler
继承ResponseEntityExceptionHandler
添加@ControllerAdvice
注解1
2
3
4
5
6
7
8
9
10
11
public class GlobeExceptionHandler extends ResponseEntityExceptionHandler {
public String handleControllerException(Throwable ex) {
// 异常逻辑处理
...
// 这里的返回值和控制器用法一样
return "后台发生异常 " + ex.getClass().getName() + ": " + ex.getMessage();
}
}
404异常统一处理
- 默认情况下 SpringBoot 是不会抛出 404 的异常的
- 首先设置
DispatcherServlet
分发器的throwExceptionIfNoHandlerFound
为true
相关代码如下 - 代码位于:
org.springframework.web.servlet.DispatcherServlet#noHandlerFound
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* No handler found -> set appropriate HTTP response status.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception if preparing the response failed
*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} - 可以看到 设置
throwExceptionIfNoHandlerFound
为true
之后 Spring 会在找不到处理时抛出异常 - 新建全局异常拦截类 参见 全局处理异常
- 重写
handleNoHandlerFoundException
父类方法中的handleExceptionInternal
方法的第二个参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package pw.yumc.demo.config;
import ...;
public class GlobeExceptionHandler extends ResponseEntityExceptionHandler {
省略部分代码...;
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
HttpServletRequest req = ((ServletWebRequest) request).getNativeRequest(HttpServletRequest.class);
Map<String, Object> map = new HashMap<>();
map.add("status", 404);
map.add("msg", "路径未找到!");
map.add("path", req.getRequestURI());
map.add("method", req.getMethod());
map.add("time", System.currentTimeMillis());
return handleExceptionInternal(ex, map, headers, status, request);
}
}
常见问题解决
POST接口提示 415 的问题
- 在开发微信支付的时候 遇到一个问题 微信回调回来的数据 接口提示 415(Unsupported Media Type) 错误
- 当时的接口是这样写的
1
2
3
4
5
6
7
8
public String index(byte[] body) {
if (body == null) {
return fail("数据为空!");
}
String notify = new String(body, "UTF-8");
log.info("收到微信回调通知: {}", notify);
} - 几经百度 发现是 方法内的参数会被 Spring 解析 但是微信传过来的又是 XML 而且没有提供
Content-Type
所以不支持 - 改成直接用流解析就行了
1
2
3
4
5
public String index(HttpServletRequest request) {
String notify = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
log.info("收到微信回调通知: {}", notify);
}