RequestBodyAdvice/ResponseBodyAdvice
顾名思义,它们和@RequestBody和@ResponseBody有关,ResponseBodyAdvice是Spring4.1推出的,另外一个是4.2后才有。它哥俩和@ControllerAdvice一起使用会有很好的化学反应
说明:这哥俩是接口不是注解,实现类需要自己提供实现
RequestBodyAdvice
官方解释为:允许body体转换为对象之前进行自定义定制;也允许该对象作为实参传入方法之前对其处理。
public interface RequestBodyAdvice { // 第一个调用的。判断当前的拦截器(advice是否支持) // 注意它的入参有:方法参数、目标类型、所使用的消息转换器等等 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); }
它的内置实现有这些:
RequestResponseBodyAdviceChain比较特殊,放在后面重点说明。RequestBodyAdviceAdapter没啥说的,因此主要看看JsonViewRequestBodyAdvice这个实现。
JsonViewRequestBodyAdvice
Spring MVC的内置实现,它支持的是Jackson的com.fasterxml.jackson.annotation.@JsonView这个注解,@JsonView一般用于标注在HttpEntity/@RequestBody上,来决定处理入参的哪些key。
该注解指定的反序列视图将传递给MappingJackson2HttpMessageConverter,然后用它来反序列化请求体(从而做对应的过滤)。
// @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]); } }
说明:这个类只要你导入了jackson的jar,默认就会被添加进去,so注解@JsonView属于天生就支持的。伪代码如下:
WebMvcConfigurationSupport: @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { ... if (jackson2Present) { adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice())); adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice())); } ... }
使用示例
@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 {}
至于我为何这么准备示例,有兴趣的同学可以了解下@JsonView
注解的用法和使用场景,你便会有所收获。
继续准备一个控制器,使用@JsonView
来指定视图类型:
@ResponseBody @PostMapping("/test/requestbody") public String testRequestBodyAdvice(@JsonView(Simple.class) @RequestBody User user) { System.out.println(user); return "hello world"; }
这时候请求(发送的body里有age这个key哦):
控制台输出:
HelloController.User(id=1, name=fsx, age=null)
可以看到即使body体里有age这个key,服务端也是不会给与接收的(age仍然为null),就因为我要的是Simple类型的JsonView。这个时候若换成@JsonView(Complex.class)那最终的结果就为:
HelloController.User(id=1, name=fsx, age=18)
使用时需要注意如下几点:
- 若不标注@JsonView注解,默认是接收所有(这是我们绝大部分的使用场景)
- @JsonView的value有且只能写一个类型(必须写)
- 若@JsonView指定的类型,在POJO的所有属性(或者set方法)里都没有@JsonView对应的指定,那最终一个值都不会接收(因为一个都匹配不上)。
@JsonView执行原理简述
简单说说@JsonView在生效的原理。它主要是在AbstractJackson2HttpMessageConverter的这个方法里(这就是为何JsonViewRequestBodyAdvice只会处理这种消息转转器的原因):
AbstractJackson2HttpMessageConverter(实际为MappingJackson2HttpMessageConverter): @Override public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { JavaType javaType = getJavaType(type, contextClass); // 把body内的东西转换为java对象 return readJavaType(javaType, inputMessage); } private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException { if (inputMessage instanceof MappingJacksonInputMessage) { Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView(); if (deserializationView != null) { return this.objectMapper.readerWithView(deserializationView).forType(javaType).readValue(inputMessage.getBody()); } } return this.objectMapper.readValue(inputMessage.getBody(), javaType); }
因为标注了@JsonView注解就使用的是它MappingJacksonInputMessage。so可见最底层的原理就是readerWithView和readValue的区别。
ResponseBodyAdvice
它允许在@ResponseBody/ResponseEntity标注的处理方法上在用HttpMessageConverter在写数据之前做些什么。
// @since 4.1 泛型T:body类型 public interface ResponseBodyAdvice<T> { boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType); @Nullable T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response); }
它的内置实现类们:
AbstractMappingJacksonResponseBodyAdvice
它做出了限定:body使用的消息转换器必须是AbstractJackson2HttpMessageConverter
才会生效。
public abstract class AbstractMappingJacksonResponseBodyAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType); } // 最终使用MappingJacksonValue来序列化body体 @Override @Nullable public final Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, ServerHttpResponse response) { if (body == null) { return null; } MappingJacksonValue container = getOrCreateContainer(body); beforeBodyWriteInternal(container, contentType, returnType, request, response); return container; } }
JsonViewResponseBodyAdvice
继承自父类,用法几乎同上面的@JsonView
,只是它是标注在方法返回值上的。
它的源码此处忽略,没什么特别的需要说明的