# @ControllerAdvice 配合 RequestBodyAdvice/ResponseBodyAdvice 使用

返回:springboot 的一些功能

@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

他有一个默认的实现

// @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

这个类只要你导入了 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
@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
  • 若不标注@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
@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

# 项目实战

# 记录接口日志信息