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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 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);
    }
  }
相关文章
|
1月前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
69 17
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
104 2
|
23天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
30天前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
45 4
|
2月前
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
43 2
Spring高手之路25——深入解析事务管理的切面本质
|
1月前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
68 8
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
89 8
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
171 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
68 2
|
2月前
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
86 2

热门文章

最新文章

推荐镜像

更多