# @ControllerAdvice 配合 RequestBodyAdvice/ResponseBodyAdvice 使用
@ControllerAdvice + @ExceptionHandler 来实现全局异常捕获;陌生在于你除了 copy 代码时看到过外,自己似乎从来没有真正使用过它。
TIP
@ControllerAdvice 使用 AOP 思想可以这么理解:此注解对目标 Controller 的通知是个环绕通知,织入的方式是注解方式,增强器是注解标注的方法。
如此就很好理解@ControllerAdvice 搭配@InitBinder/@ModelAttribute/@ExceptionHandler 起到的效果喽~
# RequestBodyAdvice
是接口不是注解,实现类需要自己提供实现;RequestBodyAdvice 是对请求响应的 json 串进行处理,一般使用环境是处理解密的json串。
需要在 controller 层中调用方法上加入@RequestMapping 和@RequestBody;后者若没有,则 RequestBodyAdvice 实现方法不执行。
允许 body 体转换为对象之前进行自定义定制;也允许该对象作为实参传入方法之前对其处理。
public interface RequestBodyAdvice {
// 第一个调用的。判断当前的拦截器(advice是否支持)
// 注意它的入参有:方法参数、目标类型、所使用的消息转换器等等
// 只有这个返回true,才会执行后面的方法
boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
// 如果body体木有内容就执行这个方法(后面的就不会再执行喽)
Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
// 重点:它在body被read读/转换**之前**进行调用的
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
// 它在body体已经转换为Object后执行。so此时都不抛出IOException了嘛~
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
他有一个默认的实现
// @since 4.2
public class JsonViewRequestBodyAdvice extends RequestBodyAdviceAdapter {
// 处理使用的消息转换器是AbstractJackson2HttpMessageConverter类型
// 并且入参上标注有@JsonView注解的
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
methodParameter.getParameterAnnotation(JsonView.class) != null);
}
// 显然这里实现的beforeBodyRead这个方法:
// 它把body最终交给了MappingJacksonInputMessage来反序列处理消息体
// 注意:@JsonView能处理这个注解。也就是说能指定把消息体转换成指定的类型,还是比较实用的
// 可以看到当标注有@jsonView注解后 targetType就没啥卵用了
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {
JsonView ann = methodParameter.getParameterAnnotation(JsonView.class);
Assert.state(ann != null, "No JsonView annotation");
Class<?>[] classes = ann.value();
// 必须指定class类型,并且有且只能指定一个类型
if (classes.length != 1) {
throw new IllegalArgumentException("@JsonView only supported for request body advice with exactly 1 class argument: " + methodParameter);
}
// 它是一个InputMessage的实现
return new MappingJacksonInputMessage(inputMessage.getBody(), inputMessage.getHeaders(), classes[0]);
}
}
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
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
这个类只要你导入了 jackson 的 jar,默认就会被添加进去
# 使用实例 RequestBodyAdvice
@Getter
@Setter
@ToString
public static class User {
@JsonView({Simple.class, Complex.class})
private Long id;
@JsonView({Simple.class, Complex.class})
private String name;
@JsonView({Complex.class})
private Integer age;
}
// 准备两个view类型(使用接口、类均可)
interface Simple {}
interface Complex {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ResponseBody
@PostMapping("/test/requestbody")
public String testRequestBodyAdvice(@JsonView(Simple.class) @RequestBody User user) {
System.out.println(user);
return "hello world";
}
1
2
3
4
5
6
2
3
4
5
6
- 若不标注@JsonView 注解,默认是接收所有(这是我们绝大部分的使用场景)
- @JsonView 的 value 有且只能写一个类型(必须写)
- 若@JsonView 指定的类型,在 POJO 的所有属性(或者 set 方法)里都没有@JsonView 对应的指定,那最终一个值都不会接收(因为一个都匹配不上)。
# ResponseBodyAdvice
是对请求响应后对结果的处理,一般使用环境是
处理加密的json串需要在 controller 层中调用方法上加入@RequstMapping 和@ResponseBody;后者若没有,则 ResponseBodyAdvice 实现方法不执行。
public interface ResponseBodyAdvice<T> {
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
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
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
@ResponseBody
@GetMapping("/test/responsebody")
@JsonView(Simple.class)
public User testResponseBodyAdvice() {
User user = new User();
user.setId(1L);
user.setName("fsx");
user.setAge(18);
return user;
}
// age属性没有
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12