web九大组件之---RequestMappingHandlerAdapter详尽解析【享学Spring MVC】(上)

简介: web九大组件之---RequestMappingHandlerAdapter详尽解析【享学Spring MVC】(上)

前言


上篇文章介绍了HandlerAdapter适配器的三种实现方式,分别实现了对"非主流"的三种控制器(Controller/HttpRequestHandler/Servlet)的适配,由于此三种控制器本身非常源生和功能简单,自然对应的适配器也非常好理解。

虽然说Spring MVC一共兼具支持了4中控制器方式,但前三种方式可谓廉颇老矣,不客气的说已经被后浪拍死在沙滩上,这就是为何大多数Java程序员并不知道前三种控制器的原因。而此处指的"后浪"便是@RequestMapping标注的控制器,它对应的适配器便是本文的主菜:RequestMappingHandlerAdapter。


RequestMappingHandlerAdapter它不仅仅之于HandlerAdapter处理适配器是最为重要的,甚至对于整个Spring MVC框架来说,此类的重要程度也是top级别。它内部含有大量的web基础组件(每个组件都是一个实用知识点)来协助完成一整个请求处理,因此它可以被描述为单个请求的调度、处理中心。


RequestMappingHandlerAdapter


用于适配@RequestMapping注解标注的Handler(Handler类型为org.springframework.web.method.HandlerMethod),继承自父类AbstractHandlerMethodAdapter。

它是自Spring3.1新增的一个适配器类(HandlerMethod也是3.1后出现的),拥有数据绑定、数据转换、数据校验、内容协商…等一系列非常高级的功能。因为有了它的存在,使得开发者几乎可以忘掉原生的Servlet API并且使用起来更加的的心用手和更加的面向对象,所以我它的出现是具有里程碑意义的。


也正是因为有了它对Servlet API的屏蔽,Spring 5.0在把Servlet容器从必选项变成可选项可以平滑过渡:即使切换了web容器(比如换成基于netty的webflux),也能做到在使用层面上对开发者是无感知的,保证了使用者的体验和降低了迁移成本,使得开发者不会抗拒reactive编程模式的到来。


它的类图谱如下:


image.png


父类AbstractHandlerMethodAdapter在上文已经有所描述,so本处就单刀直奔主题:


本类很大(1000+行代码),成员属性众多,它是请求处理的集大成者,集成了所有的用于请求处理的功能模块,所在在没有前置基础的情况下研究本类会非常吃力,但还好所有的(你没听错,所有的)组件在我博客里都有相关专题的详细讲解,若遇上明白的组件,可在我博客站内搜索关键字就能找到,本文相应的我也会对应的给些传送门


因为本来太大,所以按照传统思路之上而下的介绍恐有不妥,因此本文从初始化流程出发,一步步揭开它的执行过程。


// @since 3.1 实现了InitializingBean接口和BeanFactoryAware
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
  // 唯一构造方法:默认注册一些消息转换器。
  // 开启@EnableWebMvc后此默认行为会被setMessageConverters()方法覆盖
  public RequestMappingHandlerAdapter() {
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316
    this.messageConverters = new ArrayList<>(4);
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(stringHttpMessageConverter);
    try {
      this.messageConverters.add(new SourceHttpMessageConverter<>());
    } catch (Error err) {
      // Ignore when no TransformerFactory implementation is available
    }
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
  }
  // 此方法在此Bean初始化的时候会执行:扫描解析容器内的@ControllerAdvice...
  // 方法体看起来代码不多,但其实每个方法内部,都可谓是个庞然大物,请详细观察理解~~~~
  @Override
  public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    // 详见下面的解释分析
    initControllerAdviceCache();
    // 这三大部分,可是 "参数自动组装" 相关的组件~~~~每一份都非常的重要
    if (this.argumentResolvers == null) {
      List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
      this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
      List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
      this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
      List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
      this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
  }
}


为了突出重点,下面针对各部分方法进行逐一分析(重要):


initControllerAdviceCache()

此方法用于初始化@ControllerAdvice标注的Bean,并解析此Bean内部各部分(@ModelAttribute、@InitBinder、RequestBodyAdvice和ResponseBodyAdvice接口)然后缓存起来。


关于@ModelAttribute、@InitBinder等各部分,可站内搜索相关文章详细了解

RequestMappingHandlerAdapter:
// ======================相关成员变量======================
// 装载RequestBodyAdvice和ResponseBodyAdvice的实现类们~
private List<Object> requestResponseBodyAdvice = new ArrayList<>();
// MethodIntrospector.selectMethods的过滤器。
// 这里意思是:含有@ModelAttribute,但是但是但是不含有@RequestMapping注解的方法~~~~~
public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method -> (!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) && AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
// 标注了注解@InitBinder的方法~~~
public static final MethodFilter INIT_BINDER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
// 存储标注了@ModelAttribute注解的方法的缓存~~~~
private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache = new LinkedHashMap<>();
// 存储标注了@InitBinder注解的方法的缓存~~~~
private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>();
  private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
      return;
    }
    // 拿到容器内所有的标注有@ControllerAdvice的组件们
    // BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)
    // .filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null)
    // .map(name -> new ControllerAdviceBean(name, context)) // 使用ControllerAdviceBean包装起来,持有name的引用(还木实例化哟)
    // .collect(Collectors.toList());
    // 因为@ControllerAdvice注解可以指定包名等属性,具体可参见HandlerTypePredicate的判断逻辑,是否生效
    // 注意:@RestControllerAdvice是@ControllerAdvice和@ResponseBody的结合体,所以此处也会被找出来
    // 最后Ordered排序
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);
    // 临时存储RequestBodyAdvice和ResponseBodyAdvice的实现类
    // 它哥俩是必须配合@ControllerAdvice一起使用的~
    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
    for (ControllerAdviceBean adviceBean : adviceBeans) {
      Class<?> beanType = adviceBean.getBeanType();
      if (beanType == null) {
        throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
      }
      // 又见到了这个熟悉的方法selectMethods~~~~过滤器请参照成员变量
      // 含有@ModelAttribute,但是但是但是不含有@RequestMapping注解的方法~~~~~  找到之后放在全局变量缓存起来
      // 简单的说就是找到@ControllerAdvice里面所有的@ModelAttribute方法们
      Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
      if (!attrMethods.isEmpty()) {
        this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
      }
      // 找标注了注解@InitBinder的方法~~~(和有没有@RequestMapping木有关系了~~~)
      // 找到@ControllerAdvice里面所有的@InitBinder方法们
      Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
      if (!binderMethods.isEmpty()) {
        this.initBinderAdviceCache.put(adviceBean, binderMethods);
      }
      // 这两个接口是Spring4.1 4.2提供的,实现了这两个接口的 
      // 此处先放在requestResponseBodyAdviceBeans里面装着 最后放到全局缓存requestResponseBodyAdvice里面去
      if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
        requestResponseBodyAdviceBeans.add(adviceBean);
      }
    }
    // 这个意思是,放在该list的头部。
    // 因为requestResponseBodyAdvice有可能通过set方法进来已经有值了~~~所以此处放在头部
    if (!requestResponseBodyAdviceBeans.isEmpty()) {
      this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }
    // 输出debug日志...略(debug日志哦~)
    if (logger.isDebugEnabled()) {
      ...
    }
  }


此部分代码调理清晰,有4个作用总结如下:


  1. 找到容器内(包括父容器)所有的标注有@ControllerAdvice注解的Bean们缓存起来,然后一个个解析此种Bean
  2. 找到该Advice Bean内所有的标注有@ModelAttribute但没标注@RequestMapping的方法们,缓存到modelAttributeAdviceCache里对全局生效
  3. 找到该Advice Bean内所有的标注有@InitBinder的方法们,缓存到initBinderAdviceCache里对全局生效
  4. 找到该Advice Bean内所有实现了接口RequestBodyAdvice/ResponseBodyAdvice们,最终放入缓存requestResponseBodyAdvice的头部,他们会介入请求body和返回body


介绍完initControllerAdviceCache方法后,继续afterPropertiesSet()后续方法:初始化参数解析器、@InitBinder参数解析器、返回值解析器等。


这一步总体来说就是对参数解析、返回值解析的支持。比如对@RequestParam、@PathVariable、@RequestBody、@ResponseBody等注解的支持

  @Override
  public void afterPropertiesSet() {
    ...
    // 初始化参数解析器
    if (this.argumentResolvers == null) {
      List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
      this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 初始化@InitBinder的参数解析器
    if (this.initBinderArgumentResolvers == null) {
      List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
      this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 初始化返回值解析器
    if (this.returnValueHandlers == null) {
      List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
      this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
  }
相关文章
|
5月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
5月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
384 17
|
索引
【Flutter 开发必备】AzListView 组件全解析,打造丝滑索引列表!
在 Flutter 开发中,AzListView 是实现字母索引分类列表的理想选择。它支持 A-Z 快速跳转、悬浮分组标题、自定义 UI 和高效性能,适用于通讯录、城市选择等场景。本文将详细解析 AzListView 的核心参数和实战示例,助你轻松实现流畅的索引列表。
582 7
|
11月前
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
403 0
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
905 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
451 10
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
1425 2
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
406 2
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
1138 0

热门文章

最新文章

推荐镜像

更多
  • DNS