SpringMVC流程分析(六):SpringMVC为处理器选择合适的适配器的秘密

本系列文章皆在分析SpringMVC的核心组件和工作原理,让你从SpringMVC浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC的工作原理.

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。

作者:毅航😜

前言

SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器 中,我们通过对doDispatch方法中getHandler方法分析由浅入深的讨论了HandlerMapping组件的相关内容。通过分析我们知道了HanlderMapping的主要工作就是根据前端传来的请求,然后找到合适的处理器

此外,在分析HanlderMapping的过程中我们注意到在getHandler方法的内部会遍历全部的HanlderMapping信息,然后逐一进行匹配。如果找到合适的处理器组件,则返回一个HandlerExecutionChain对象。

而在上一节SpringMVC流程分析(五):HandlerInterceptor组件------SpingMVC中优雅的对请求中,我们指出HandlerExecutionChain其实相当于对处理器的一种封装,其内部会包含处理器handler和拦截器HandlerInterceptor。其中。处理器很好理解,无非就是在程序中被我们使用@GetMapping,@PostMapping注解所标记的方法。

进一步,通过上述doDispatch的调用链我们注意到,当通过getHanlder方法获取到一个HandlerExecutionChain对象后,其后续会执行getHandlerAdapter方法并获取一个HandlerAdapter对象。 这个HandlerAdapter又是什么呢?别急,本章我们便来揭开HandlerAdapter神秘面纱。

(注:本章我们主要探究getHandlerAdapter(mappedHandler.getHandler())ha.handle两个方法的执行逻辑。)

下图展示了本系列文章重点分析的组件信息,其中 HandlerAdapter为本文分析的重点。

getHandlerAdapter的相关逻辑

首先来分析getHandlerAdapter的相关逻辑。

java 复制代码
protected HandlerAdapter getHandlerAdapter(Object handler) 
                            throws ServletException {
        
    if (this.handlerAdapters != null) {
	for (HandlerAdapter adapter : this.handlerAdapters) {
		if (adapter.supports(handler)) {
			return adapter;
		}
	}
    // 省略异常处理
}

可以注意到,getHandlerAdapter的处理逻辑为遍历容器中的全部的HandlerAdapter,并找到一个可以处理当前处理器的HandlerAdapter返回。

如果你看过之前我们对于HanlderMapping的分析,你会发现,此处的逻辑同getHandler方法的逻辑十分类似。在SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器 中分析HandlerMapping时,我们提到getHandler的逻辑为:"遍历容器所有的HandlerMapping信息,寻找到一个可以处理当前请求的Handler,并将其构建为HandlerExecutionChain的一个对象。"

如何对处理器进行封装

java 复制代码
public void doDipatch(HttpServletRequest request, HttpServletResponse response) {
    
    // ... 省略其他无关代码		
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // ... 省略其他无关代码			
   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}

我们注意到,doDispatch调用getHandlerAdapter时,会首先调用mappedHandler.getHandler(),而其作用在于获取到HandlerExecutionChain中的处理器信息。换言之,对于getHandlerAdapter而言,其需要将一个处理器作为入参。此时我们不免会有这样的疑惑,我们知道所谓的处理器本质就是处理器某请求的逻辑信息,既然HandlerExecutionChain已经持有处理器了,为什么不直接调用,反而又要根据处理器获取到一个HandlerAdapter信息,然后再将处理器传入到HandlerAdapterhandler方法呢?

此时你可能会想,这里使用HandlerAdapter是不是多此一举呢?我明明直接获取HandlerExecutionChain中的处理器,然后执行相关逻辑就可以了,为什么又要使用这个HandlerAdapter呢?

那这个HandlerAdapter是多余的吗?当然不是! 你之所以认为只要调用处理器就可以,完全是基于这样一种假设,即处理器是一个方法,这个方法内部定义了处理请求的逻辑信息,所以你会简单的认为执行处理逻辑本质就是调用方法。

但是别忘了,在SpringMVC中不仅标有@RequestMapping的方法会被认为是处理器,同时实现Controller接口,重写其中的handlerRequest的方法也能在SpringMVC中定义一个处理器。此外,还可以实现HttpRequestHandler 接口,重写其中的handleRequest方法也可以实现。

综上所示,在SpringMVC中定义处理的方法主要有两种方式:

  1. 使用@RequestMapping标注的方法
  2. 实现ControllerHttpRequestHandler接口,并重写其中的handleRequest方法

针对上述两种情况,SpringMVC框架的开发者所面临问题就是,针对处理器的不同实现方式,究竟该如何处理才能处理器的逻辑正常执行?

答案就是"适配"。但不同处理器在实现方式还是有所差异的,例如,基于接口Controller和注解@RequestMapping修饰的方法就是两种不同的实现方式。 那在适配时该如何处理呢?很简单,只需提供一个判断方法即可,用以判断处理器的实现方式,从而为其选择合适的"适配器"。 进一步,当完成适配后,我们需要仅仅是调用其中的方法,就能完成逻辑的执行。

事实上,这就是HandlerAdapter为我们做的事情。明白了这些,相信接下来再看HandlerAdapter的相关分析一定会一种拨云见日的感觉。

深入理解HandlerAdapter

通过之前的分析,我们可以知道HandlerAdapter的作用是将不同类型的处理器进行适配,从而实现统一的请求处理流程。

HandlerAdapter的体系结构

其中:

  1. RequestMappingHandlerAdapter:这是 Spring MVC 默认使用的 HandlerAdapter,用于处理带有 @RequestMapping 注解的 Controller 方法。它支持将请求参数绑定到方法参数、处理返回值并选择视图解析器等。
  2. HttpRequestHandlerAdapter:用于处理实现了 HttpRequestHandler 接口的 Controller,这些 Controller 直接操作 HttpServletRequest HttpServletResponse 对象,通常用于返回原始的响应数据,如文件下载等。
  3. SimpleControllerHandlerAdapter:用于处理实现了 Controller 接口的 Controller,这种 Controller 需要实现 handleRequest 方法来处理请求。

此外,这些HandlerAdapter实现都遵循了统一的接口和抽象,使得 Spring MVC 可以根据请求的类型选择合适的 HandlerAdapter 来进行处理。接口HandlerAdapter定义的内容如下:

java 复制代码
public interface HandlerAdapter {
    
    /**
     *  判断能否适配当前处理器
     * */
    boolean supports(Object handler);

    /**
     *  执行处理器的方法
     * */
    @Nullable
    ModelAndView handle(HttpServletRequest request,  HttpServletResponse response, 
                                       Object handler) throws Exception;

}

可以看到HandlerAdapter内部方法的作用如下:

  1. support 判断当前当前适配器能否适配该处理器
  2. hanlder 执行处理中的处理逻辑

当我们知晓了HandlerAdapter的类结构关系和内部定义的方法后,再回看之前getHandlerAdapter中的代码是不是感觉getHandlerAdapter逻辑十分简单?

让我们把视角再拉回到getHandlerAdapter中,我们注意到,既然getHandlerAdapter内部会遍历全部的HandlerAdapter组件,并通过其Support方法为处理器选择合适的适配器,但我们使用SpringMVC时好像并没有显示的配置过有关适配器的相关信息,那这个适配器是在哪加载的呢?

别着急,接下来我们便来分析在SpringMVC内部在加载时默认会为我们加载哪些适配器。

HandlerAdapter初始化的相关逻辑

SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器 中有提到过,"SpringMVC框架在使用过程中,通常会遵守一种约定优于配置的原则,即通过一系列约定和默认配置来减少开发人员需要手动进行配置的工作,从而提高开发效率和降低代码复杂性"

HandlerMapping组件的初始化的相关逻辑在 DispatcherServletinitHandlerAdapters(ApplicationContext context) 方法中进行了定义。 而该方法会在 onRefresh 方法被调用,初始化 HandlerAdapter 组件。initHandlerAdapters方法的逻辑如下:

java 复制代码
private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;
    
    if (this.handlerAdapters == null) {
        // 调用初始策略
        this.handlerAdapters = getDefaultStrategies(context,
                                        HandlerAdapter.class);
        // 省略其他无关代码.....
    }
}

上述代码逻辑很简单,如果当前容器中没有找到相关的HandlerAdapter,则会通过方法 getDefaultStrategies加载默认的适配信息。

而在SpringMVC中,此处定义的 getDefaultStrategies逻辑,会默认从配置文件中加载如下三个HandlerAdapter信息。

ini 复制代码
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.
mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

即默认会加载HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter

至此,我们以doDispatch中的getHanlderAdapter方法为起点,分析发现其内部逻辑为遍历容器内部的HandlerAdapter信息。进一步,以此为基础我们分析HandlerAdapter的类结构关系和功能方法。更进一步,我们分析讨论了SpringMVC内部HandlerAdapter的初始过程。

接下来,我们便开始着重分析doDispathcer中的另外一个方法ha.handler()的相关内容。事实上,ha.hanlder()背后的逻辑就是调用HandlerAdapterhandler方法。

适配器中hanlder的处理逻辑

对于适配器HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter来说,其主要用于处理实现ControllerHttpRequestHandler接口的处理器,其中的handler方法的逻辑相对简单,在此便不加叙述,我们重点关注RequestMappingHandlerAdapterhandler方法的相关逻辑。

抽象基类:AbstractHandlerMethodAdapter

通过之前HandlerAdapter的结构关系可以知道,对于RequestMappingHandlerAdapter而言,其会继承自AbstractHandlerMethodAdapter。而AbstractHandlerMethodAdapter的逻辑如下:

java 复制代码
public abstract class AbstractHandlerMethodAdapter 
                extends WebContentGenerator 
                implements HandlerAdapter, Ordered {

       
      // .......省略其他无关代码

  
       @Override
       @Nullable
       public final ModelAndView handle(HttpServletRequest request,
                                   HttpServletResponse response, 
                                   Object handler)
                                                 throws Exception {

          return handleInternal(request, response,
                          (HandlerMethod) handler);
       }
   

    protected abstract ModelAndView handleInternal(HttpServletRequest request,
                              HttpServletResponse response, 
                      HandlerMethod handlerMethod) throws Exception;


   // .......省略其他无关代码
}

可以看到AbstractHandlerMethodAdapter中的hanlder的处理逻辑会委托给子类的handleInternal进行实现,所以如果我们要讨论RequestMappingHandlerAdapter中对于处理器的操作逻辑,那我们只需关注其中的handleInternal即可。

(注:AbstractHandlerMethodAdapter的逻辑其实很简单,其将一部分执行逻辑都交给了子类处理,但如果不看AbstractHandlerMethodAdapter上来直接分析RequestMappingHandlerAdapter中的handleInternal方法,会很跳跃,不利于理解。)

内部核心成员变量

RequestMappingHandlerAdapter HandlerAdapter接口的实现类,用于适配处理带有 @RequestMapping注解的方法,同时处理参数解析、方法调用和返回值处理等操作。 其内部关键成员信息如下所示:

其中:

  • HandlerMethodArgumentResolverComposite argumentResolvers:参数处理器组合对象
  • HandlerMethodReturnValueHandlerComposite returnValueHandlers:返回值处理器组合对象
  • List<HttpMessageConverter<?>> messageConvertersHTTP 消息转换器集合对象
  • List<Object> requestResponseBodyAdviceRequestResponseAdvice 集合对象

由于被@RequestMapping注解标注的方法会被认为是处理器,所以执行处理器的本质逻辑就在于方法的执行。而从一个http请求进入到SpringMVC开始,直到为http请求找到对应处理器的这一过程中,处理器所需的参数信息仍在http请求中,其并未得到解析,所以如果要保证处理器可以执行,则需要面临参数解析提供方法所需参数,返回值处理解决返回值类型映射等问题。所以RequestMappingHandlerAdapter内部才会有上图所示的成员信息。

进一步,由于RequestMappingHandlerAdapter会实现InitializingBean接口,因此上述成员信息的初始化操作全部放到了方法afterPropertiesSet中进行初始化。相关执行流程图如下所示:

执行HanlderMapping的处理"方法"

如之前分析中我们提到,在基类AbstractHandlerMethodAdapter中会将hanlder的处理方法的相关的逻辑委托于子类的handleInternal进行实现。接下来,我们看看RequestMappingHandlerAdapter中的handleInternal究竟做了哪些工作。

RequestMappingHandlerAdapter # handleInternal

java 复制代码
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, 
                                      HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
  
    
    // .... 省略其他无关代码
  
    // <2> 调用 HandlerMethod 方法
    mav = invokeHandlerMethod(request, response, handlerMethod);
    
    // .... 省略其他无关代码
    return mav;
}

可以看到,对于RequestMappingHandlerAdapter中的handleInternal而言,其关键逻辑在于invokeHandlerMethod,从方法名字就可以看出,其主要任务肯定是执行 HandlerMethod

此时,你可能会疑惑,我知道HandlerMapping、HandlerHandlerAdapter,那这个HandlerMethod又是什么呢?

简单来说,在SpringMVC中被@RequestMapping标注的方法会被解析为一个HandlerMethod对象。而HandlerMethod是一个用于封装处理http请求的方法及其相关信息的类。

进一步,当一个http请求到达时,Spring MVC会根据请求的URL路径和HTTP方法,查找匹配的HandlerMethod,然后将请求参数解析并传递给该方法。HandlerMethod对象包含了以下重要的信息:

  1. 控制器对象:指定了处理请求的控制器实例。
  2. 处理方法:指定了要执行的具体处理方法。
  3. 方法参数:包含了方法的参数信息,这些参数会从HTTP请求中提取而来。
  4. 返回值类型:定义了处理方法的返回值类型。

总结来看,HandlerMethod对象的作用是使得Spring MVC能够在请求到达时,动态地找到匹配的处理方法并进行调用其中的处理方法。

看到此,相信你一定会有一种恍然大悟的感觉,这所谓的handler背后的逻辑无非就是调用被@ReqeustMapping标注的方法。同时,在处理过程中还会依赖一些额外的组件信息来完成参数解析,返回值处理等额外的操作。

至于如何执行被@ReqeustMapping标注的方法的方法,答案其实已经很明显了,其背后的逻辑肯定是依赖java中的反射机制。

总结

Spring MVC 通过 HandlerMapping 组件会为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器( handler )和拦截器( interceptors **)。

在之前SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器一文中我们有提到处理器的实现有多种,例如,通过实现 Controller 接口、HttpRequestHandler 接口,或者使用@RequestMapping 注解将方法作为一个处理器等。

这就导致 Spring MVC 无法直接执行这个处理器,所以这里需要一个处理器适配器,由它去执行处理器。而HandlerAdapter 处理器适配器对应的也有多种,哪种适配器支持处理这种类型的处理器,则由该适配器去执行,如下:

  • HttpRequestHandlerAdapter:执行实现了 HttpRequestHandler 接口的处理器
  • SimpleControllerHandlerAdapter:执行实现了 Controller 接口的处理器
  • SimpleServletHandlerAdapter:执行实现了 Servlet 接口的处理器
  • RequestMappingHandlerAdapter :执行 HandlerMethod 类型的处理器,也就是通过 @RequestMapping 等注解标注的方法

本文重点分析了 RequestMappingHandlerAdapter 对象,因为这种方式是目前使用最普遍的,其他类型的 HandlerAdapter 处理器适配器做了解即可

相关推荐
_江南一点雨1 小时前
SpringBoot 3.3.5 试用CRaC,启动速度提升3到10倍
java·spring boot·后端
深情废杨杨1 小时前
后端-实现excel的导出功能(超详细讲解)
java·spring boot·excel
酸奶代码1 小时前
Spring AOP技术
java·后端·spring
代码小鑫1 小时前
A034-基于Spring Boot的供应商管理系统的设计与实现
java·开发语言·spring boot·后端·spring·毕业设计
paopaokaka_luck2 小时前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法
程序猿麦小七2 小时前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~2 小时前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong2 小时前
SpringBoot后端解决跨域问题
spring boot·后端·python
A陈雷2 小时前
springboot整合elasticsearch,并使用docker desktop运行elasticsearch镜像容器遇到的问题。
spring boot·elasticsearch·docker
.生产的驴2 小时前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq