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

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



相关文章
|
12天前
|
存储 人工智能 Java
【保姆级图文详解】基于Spring AI的旅游大师应用开发、多轮对话、文件持久化、拦截器实现
【保姆级图文详解】基于Spring AI的旅游大师应用开发、多轮对话、文件持久化、拦截器实现
137 2
【保姆级图文详解】基于Spring AI的旅游大师应用开发、多轮对话、文件持久化、拦截器实现
|
4月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
155 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
4月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
190 0
|
4月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
129 0
|
4月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
155 0
|
4月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
134 0
|
3天前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
|
3天前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
|
18天前
|
人工智能 安全 Java
Spring Boot 过滤器 拦截器 监听器
本文介绍了Spring Boot中的过滤器、拦截器和监听器的实现与应用。通过Filter接口和FilterRegistrationBean类,开发者可实现对请求和响应的数据过滤;使用HandlerInterceptor接口,可在控制器方法执行前后进行处理;利用各种监听器接口(如ServletRequestListener、HttpSessionListener等),可监听Web应用中的事件并作出响应。文章还提供了多个代码示例,帮助读者理解如何创建和配置这些组件,适用于构建更高效、安全和可控的Spring Boot应用程序。
|
6月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
278 29