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);
    }
  }
相关文章
|
6月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
NoSQL 安全 Java
深入理解 RedisConnectionFactory:Spring Data Redis 的核心组件
在 Spring Data Redis 中,`RedisConnectionFactory` 是核心组件,负责创建和管理与 Redis 的连接。它支持单机、集群及哨兵等多种模式,为上层组件(如 `RedisTemplate`)提供连接抽象。Spring 提供了 Lettuce 和 Jedis 两种主要实现,其中 Lettuce 因其线程安全和高性能特性被广泛推荐。通过手动配置或 Spring Boot 自动化配置,开发者可轻松集成 Redis,提升应用性能与扩展性。本文深入解析其作用、实现方式及常见问题解决方法,助你高效使用 Redis。
1226 4
|
负载均衡 Java Nacos
Spring Cloud五大组件
Spring Cloud五大组件
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
462 0
|
11月前
|
存储 算法 安全
JWT深度解析:现代Web身份验证的通行证为什么现在都是JWT为什么要restful-优雅草卓伊凡
JWT深度解析:现代Web身份验证的通行证为什么现在都是JWT为什么要restful-优雅草卓伊凡
596 41
JWT深度解析:现代Web身份验证的通行证为什么现在都是JWT为什么要restful-优雅草卓伊凡
|
9月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
657 0
|
9月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
906 0
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
1358 61
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
965 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
JSON JavaScript 前端开发
蓝桥杯web组赛题解析和杯赛技巧
本文作者是一位自学前端两年半的大一学生,在第十五届蓝桥杯Web组比赛中获得省一和国三。文章详细解析了比赛题纲,涵盖HTML、CSS、JavaScript、Echarts和Vue等技术要点,并分享了备赛技巧和比赛经验。作者强调了多写代码和解题思路的重要性,同时提供了省赛和国赛的具体流程及注意事项。希望对参赛者有所帮助。
1470 11

热门文章

最新文章

推荐镜像

更多
  • DNS
  • 下一篇
    开通oss服务