掌握@ControllerAdvice配合RequestBodyAdvice/ResponseBodyAdvice使用,让你的选择不仅仅只有拦截器【享学Spring MVC】(中)

简介: 掌握@ControllerAdvice配合RequestBodyAdvice/ResponseBodyAdvice使用,让你的选择不仅仅只有拦截器【享学Spring MVC】(中)

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);
}


它的内置实现有这些:


image.png


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哦):


image.png


控制台输出:

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)


使用时需要注意如下几点:


  1. 若不标注@JsonView注解,默认是接收所有(这是我们绝大部分的使用场景)
  2. @JsonView的value有且只能写一个类型(必须写)
  3. 若@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);
}

它的内置实现类们:


image.png


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,只是它是标注在方法返回值上的。

它的源码此处忽略,没什么特别的需要说明的



相关文章
|
18天前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
23天前
|
XML JSON 数据库
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
这篇文章详细介绍了RESTful的概念、实现方式,以及如何在SpringMVC中使用HiddenHttpMethodFilter来处理PUT和DELETE请求,并通过具体代码案例分析了RESTful的使用。
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
|
20天前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
|
1月前
|
JSON 前端开发 Java
Spring MVC返回JSON数据
综上所述,Spring MVC提供了灵活、强大的方式来支持返回JSON数据,从直接使用 `@ResponseBody`及 `@RestController`注解,到通过配置消息转换器和异常处理器,开发人员可以根据具体需求选择合适的实现方式。
75 4
|
1月前
|
XML 前端开发 Java
Spring MVC接收param参数(直接接收、注解接收、集合接收、实体接收)
Spring MVC提供了灵活多样的参数接收方式,可以满足各种不同场景下的需求。了解并熟练运用这些基本的参数接收技巧,可以使得Web应用的开发更加方便、高效。同时,也是提高代码的可读性和维护性的关键所在。在实际开发过程中,根据具体需求选择最合适的参数接收方式,能够有效提升开发效率和应用性能。
66 3
|
1月前
|
XML 前端开发 Java
Spring MVC接收param参数(直接接收、注解接收、集合接收、实体接收)
Spring MVC提供了灵活多样的参数接收方式,可以满足各种不同场景下的需求。了解并熟练运用这些基本的参数接收技巧,可以使得Web应用的开发更加方便、高效。同时,也是提高代码的可读性和维护性的关键所在。在实际开发过程中,根据具体需求选择最合适的参数接收方式,能够有效提升开发效率和应用性能。
57 2
|
1月前
|
前端开发 JavaScript Java
Spring Boot中使用拦截器
本节主要介绍了 Spring Boot 中拦截器的使用,从拦截器的创建、配置,到拦截器对静态资源的影响,都做了详细的分析。Spring Boot 2.0 之后拦截器的配置支持两种方式,可以根据实际情况选择不同的配置方式。最后结合实际中的使用,举了两个常用的场景,希望读者能够认真消化,掌握拦截器的使用。
|
2月前
|
前端开发 Java 应用服务中间件
我以为我对Spring MVC很了解,直到我遇到了...
所有人都知道Spring MVC是是开发的,却鲜有人知道Spring MVC的理论基础来自于1978 年提出MVC模式的一个老头子,他就是Trygve Mikkjel Heyerdahl Reenskaug,挪威计算机科学家,名誉教授。Trygve Reenskaug的MVC架构思想早期用于图形用户界面(GUI) 的软件设计,他对MVC是这样解释的。MVC 被认为是解决用户控制大型复杂数据集问题的通用解决方案。最困难的部分是为不同的架构组件想出好的名字。模型-视图-编辑器是第一个。
我以为我对Spring MVC很了解,直到我遇到了...
|
2月前
|
前端开发 Java API
Spring Boot 中的 MVC 支持
### Spring Boot 注解摘要 - **@RestController** - **@RequestMapping** - **@PathVariable** - **@RequestParam** - **@RequestBody**
24 2
|
26天前
|
前端开发 Java Spring
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
39 0
下一篇
DDNS