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

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

使用示例


准备一个控制器如下(其它的同上):

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


请求结果如下:


image.png



它的使用注意事项同上,基本原理同上(writerWithView/writer的区别)。

RequestResponseBodyAdviceChain

它是代理模式的实现,用于执行指定的RequestBodyAdvice/ResponseBodyAdvice们,实现方式基本同前面讲过多次的xxxComposite模式。


需要注意的是,两个advice的support()方法都只只只在这里被调用。所以很容易相想到Spring调用advice增强时最终调用的都是它,它就是一个门面。



// @since 4.2  请注意:它的访问权限是default哦
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
  //它持有所有的,记住是所有的advice们
  private final List<Object> requestBodyAdvice = new ArrayList<>(4);
  private final List<Object> responseBodyAdvice = new ArrayList<>(4);
  // 可以看到这是个通用的方法。内来进行区分存储的   getAdviceByType这个区分方法可以看一下
  // 兼容到了ControllerAdviceBean以及beanType本身
  public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
    this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
    this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
  }
  @Override
  public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
    throw new UnsupportedOperationException("Not implemented");
  }
  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    throw new UnsupportedOperationException("Not implemented");
  }
  // 可以看到最终都是委托给具体的Advice去执行的(supports方法)
  // 特点:符合条件的所有的`Advice`都会顺序的、依次的执行
  @Override
  public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
    for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
      if (advice.supports(parameter, targetType, converterType)) {
        request = advice.beforeBodyRead(request, parameter, targetType, converterType);
      }
    }
    return request;
  }
  ... // 其余方法略。处理逻辑同上顺序执行。
  // 最重要的是如下这个getMatchingAdvice()匹配方法
  private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
    // 简单的说你想要的是Request的还是Response的List呢?
    List<Object> availableAdvice = getAdvice(adviceType);
    if (CollectionUtils.isEmpty(availableAdvice)) {
      return Collections.emptyList();
    }
    List<A> result = new ArrayList<>(availableAdvice.size());
    for (Object advice : availableAdvice) {
      if (advice instanceof ControllerAdviceBean) {
        ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
        // 这里面会调用beanTypePredicate.test(beanType)方法
        // 也就是根据basePackages等等判断此advice是否是否要作用在本类上
        if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
          continue;
        }
        advice = adviceBean.resolveBean();
      }
      // 当前的advice若是满足类型要求的,那就添加进去  最终执行切面操作
      if (adviceType.isAssignableFrom(advice.getClass())) {
        result.add((A) advice);
      }
    }
    return result;
  }
}


这是批量代理模式的典型实现,Spring框架中不乏这种实现方式,对使用者非常友好,也很容易控制为链式执行或者短路执行。


初始化解析流程分析


我们知道所有的xxxBodyAdvice最终都是通过暴露的RequestResponseBodyAdviceChain来使用的,它内部持有容器内所有的Advice的引用。由于RequestResponseBodyAdviceChain的访问权限是default,所以这套机制完全由Spring内部控制。

他唯一设值处是:AbstractMessageConverterMethodArgumentResolver。


AbstractMessageConverterMethodArgumentResolver(一般实际为RequestResponseBodyMethodProcessor):
  // 唯一构造函数,指定所有的advices
  public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
    Assert.notEmpty(converters, "'messageConverters' must not be empty");
    this.messageConverters = converters;
    this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
    this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
  }


此构造函数在new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)时候调用,传进来的requestResponseBodyAdvice就刚好是在初始化RequestMappingHandlerAdapter的时候全局扫描进来的所有的增强器们。

使用场景

本文介绍了@ControllerAdvice的使用以及它的解析原理,最重要的是结合RequestBodyAdvice/ResponseBodyAdvice来实现类似拦截器的效果。在现在前后端分离的开发模式下,大部分的情况下的请求是json格式,因此此种方式会有很大的用武之地,我举例几个经典使用场景供以参考:


  1. 打印请求、响应日志
  2. 对参数解密、对响应加密
  3. 对请求传入的非法字符做过滤/检测


总结


本文旨在介绍@ControllerAdvice和RequestBodyAdvice/ResponseBodyAdvice的作用,为你解决在解决一些拦截问题时提供一个新的思路,希望能够对你的眼界、代码结构上的把控能有所帮助。

同时也着重介绍了@JsonView的使用:它可以放入参时接收指定的字段;也可以让返回值中敏感字段(如密码、盐值等)不予返回,可做到非常灵活的配置和管理,实现一套代码多处使用的目的,提高集成程度。


咀咒,需要注意的是:xxxBodyAdvice虽然使用方便,但是它的普适性还是没有HandlerInterceptor那么强的,下面我列出使用它的几点局限/限制:


  1. xxxAdvice必须被@ControllerAdvice注解标注了才会生效,起到拦截的效果
  2. 它只能作用于基于消息转换器的请求/响应(参考注解@RequestBody/@ResponseBody)
  3. 当然,只能作用于@RequestMapping模式下的处理器模型上
相关文章
|
1月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
1月前
|
缓存 监控 Java
《深入理解Spring》拦截器(Interceptor)——请求处理的艺术
Spring拦截器是Web开发中实现横切关注点的核心组件,基于AOP思想,可在请求处理前后执行日志记录、身份验证、权限控制等通用逻辑。相比Servlet过滤器,拦截器更贴近Spring容器,能访问Bean和上下文,适用于Controller级精细控制。通过实现`HandlerInterceptor`接口的`preHandle`、`postHandle`和`afterCompletion`方法,可灵活控制请求流程。结合配置类注册并设置路径匹配与执行顺序,实现高效复用与维护。常用于认证鉴权、性能监控、统一异常处理等场景,提升应用安全性与可维护性。
|
8月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
475 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
8月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
725 0
|
8月前
|
前端开发 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 即可验证参数是否正确接收。
472 0
|
8月前
|
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 等。
417 0
|
4月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
324 0
|
4月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
153 0
|
4月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
265 0
|
存储 人工智能 Java
【图文详解】基于Spring AI的旅游大师应用开发、多轮对话、文件持久化、拦截器实现
【图文详解】基于Spring AI的旅游大师应用开发、多轮对话、文件持久化、拦截器实现
783 0