Spring源码之Spring MVC

1.什么是Spring MVC

Spring MVC是基于Servlet API构建的原始Web框架,从一开始就包含在了Spring框架中,作为其中的一部分,对应为spring-webmvc包 。它遵循Model-View-Controller(MVC)设计模式,提供了一种清晰的方式来分离业务逻辑、用户界面表示以及用户输入处理。

Model-View-Controller(MVC):是一种软件架构模式,用于组织代码并分离关注点,广泛应用于用户界面的开发中。该模式将相关程序逻辑划分为三个相互关联的组成部分:模型(Model)、视图(View)和控制器(Controller) 。
模型(Model):承担着管理应用程序数据和业务逻辑的重要责任。包含的主要职责有:①作为数据访问对象(Data Access Objects,DAO),用于与数据存储交互,以及实体类来表示应用程序中的核心数据结构;②负责数据的通知和变化,确保数据变化时其他组件(如视图和控制器)能够及时获取到更新的数据。
视图(View):负责将模型的数据以用户友好的方式呈现给用户,并接收用户的输入。视图通常包括各种用户界面组件,如文本框、按钮、下拉菜单等,以及对数据的展示和呈现逻辑。
控制器(Controller):它是模型和视图之间的桥梁,负责解释用户的输入,控制用户输入的流程,并更新模型和视图。控制器接收来自视图的动作事件,然后根据这些事件来决定如何操作模型(例如更新数据)以及选择哪个视图来更新用户界面。

2.Spring MVC核心概念

2.1 核心组件

DispatcherServlet:前端控制器,负责接收客户端请求,并将请求分发给合适的处理程序。作为请求处理流程的入口,其实现了Servlet接口,对应UML类图如下所示:
UML类图-DispatcherServlet

HandlerMapping:处理器映射器,用于查找处理请求的控制器。基础实现类有:

  • RequestMappingHandlerMapping:根据 @RequestMapping 注解来映射处理器
  • SimpleUrlHandlerMapping:支持根据URL与Bean名称/Bean实例来映射处理器
  • BeanNameUrlHandlerMapping:基于 URL 的 Bean 名称来映射处理器
    其中RequestMappingHandlerMapping是最常用的。

HandlerAdapter:处理器适配器,负责调用具体的控制器方法。最常用的实现类为RequestMappingHandlerAdapter。
ViewResolver:视图解析器,用于解析逻辑视图名,返回具体的视图对象。

  • InternalResourceViewResolver:支持JSP视图,可以集成JSTL。
  • UrlBasedViewResolver:通用视图解析器,可以创建任意类型的视图。
  • FreeMarkerViewResolver:用于解析FreeMarker模板。
  • BeanNameViewResolver:根据视图名从Spring容器中查找视图bean。

ModelAndView:包含模型数据和视图名的对象,用于将数据传递给视图层。

2.2 请求处理流程

Spring MVC的请求处理核心流程如下图所示:
Spring MVC请求处理核心流程图
总结下来就是,当客户端发起请求时,请求首先到达DispatcherServlet,DispatcherServlet将执行以下操作:

  1. 请求分发:根据请求的URL找到对应的HandlerMapping,HandlerMapping将返回一个HandlerExecutionChain对象,该对象包含了一个或多个Handler对象以及拦截器(如果有的话)。
  2. 调用处理器:DispatcherServlet使用HandlerAdapter来调用Handler对象中的方法。
  3. 处理请求:Handler对象处理请求后返回一个ModelAndView对象,该对象包含了模型数据和视图名。
  4. 视图渲染:DispatcherServlet使用ViewResolver解析视图名,得到具体的视图对象,然后将模型数据传给视图进行渲染。
  5. 返回响应:最终的HTML页面被返回给客户端。

3.WEB示例

在开始源码分析之前,我们先看一个web项目示例。该项目基于web.xml的方式进行配置的(servlet3.0开始支持注解形式,受限于篇幅将在后续文章中分析),具体内容如下:
项目目录:

└─src
    └─main
        ├─java
        │  └─spring
        │      └─mvc
        │          └─controller
        │                     └─TestRestController
        ├─resources
        │         └─dispatcher-servlet.xml
        └─web
            └─WEB-INF
                    └─web.xml

web.xml

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <!-- 配置Spring MVC的核心控制器 -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 配置dispatcher处理的请求路径信息 -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

dispatcher-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd
                           http://www.springframework.org/schema/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
    <!-- 启用基于注解的控制器 -->
    <mvc:annotation-driven />
    <!-- 组件扫描 -->
    <context:component-scan base-package="spring.mvc.controller" />
</beans>

TestController

package spring.mvc.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/test")
public class TestRestController {

    @GetMapping("/message")
    public String test() {
        return "hello mvc";
    }
}

4.Spring MVC初始化

Spring MVC初始化分为两部分,第一部分是WebApplicationContext初始化(即Spring容器),第二部分是DispatcherServlet初始化。因为Spring MVC是Spring框架的子模块,因此其运行需要依赖Spring容器,接下来将先从WebApplicationContext初始化开始进行分析。

4.1 WebApplicationContext初始化

WebApplicationContext初始化依赖于tomcat对servlet进行初始化步骤,因此我们在web.xml中配置的servlet:DispatcherServlet在初始化时会触发WebApplicationContext的初始化,其UML时序图如下图所示:
UML时序图-WebApplicationContext初始化
从上图可知,WebApplicationContext初始化的核心步骤在DispatcherServlet类的父类FrameworkServlet类的createWebApplicationContext()方法中,其核心源码如下:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // 获取使用的Spring容器类,对应为内部默认的类型:XmlWebApplicationContext
   Class<?> contextClass = getContextClass();
   // 省略部分代码...
   // 通过反射的方式实例化Spring容器
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    // 设置环境变量信息
   wac.setEnvironment(getEnvironment());
   // 设置父容器 通常情况下我们会通过在web.xml中设置listener为ContextLoaderListener,进行Spring父容器初始化,用于service、mapper等Bean的管理
   // 通过上述方式配置的Spring容器即为父容器
   wac.setParent(parent);
   // 获取上下文配置路径,从web.xml中的servlet->init-param获取配置的信息
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   // 将servlet容器的上下文、配置等信息设置给Spring容器,
   // 并通过执行XmlWebApplicationContext的父类AbstractApplicationContext#refresh()方法,进行Spring容器初始化
   configureAndRefreshWebApplicationContext(wac);
   return wac;
}

其内部调用的configureAndRefreshWebApplicationContext()方法,其核心源码如下:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   // 省略部分代码...
   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   // 添加应用消息监听器,用于接收容器启动事件ContextRefreshedEvent
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   // 提前将servlet容器涉及的相关配置信息初始化到Spring容器的属性集里
   // 确保Spring容器初始化能取到依赖于servlet容器的配置属性值
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
   }
    // 扩展口,用于在Spring容器初始化前做一些扩展操作
   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   // 进行Spring容器的初始化
   wac.refresh();
}

4.2 DispatcherServlet初始化

DispatcherServlet初始化此处涉及的不是servlet的初始化(tomcat启动后,会将配置在web.xml中的servlet进行实例及初始化,从前面步骤中完成的Spring容器的初始化),而是对HandlerMapping、HandlerAdapter等用于请求转发、处理操作的相关配置解析初始化操作,以及这部分配置信息与DispatcherServlet结合,完成DispatcherServlet完整初始化过程。
接下来将针对这两部分进行分析。

4.2.1 配置解析

配置涉及HandlerMapping、HandlerAdapter、ViewResolver的解析及生成,整体涉及BeanDefinition扫描、加载、注册,以及对应Bean的实例及初始化,接下来将从这两部分进行分析。

4.2.1.1 AnnotationDrivenBeanDefinitionParser

HandlerMapping、HandlerAdapter、ViewResolver涉及的BeanDefinition扫描、加载、注册是由AnnotationDrivenBeanDefinitionParser类负责的,其内部核心方法为parse()。

AnnotationDrivenBeanDefinitionParser这个类通过解析web.xml中的servlet->init-param中设置的配置文件,完成配置类的BeanDefinition注册。主要完成了:
①根据xml配置文件进行自定义配置的BeanDefinition注册;
②默认配置的BeanDefinition注册;
其核心源码如下:

public BeanDefinition parse(Element element, ParserContext context) {
   Object source = context.extractSource(element);
   XmlReaderContext readerContext = context.getReaderContext();
   // 省略部分代码...
   
   // 进行自定义配置的BeanDefinition注册
   // RequestMappingHandlerMapping
   RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
   // 省略属性赋值相关代码...
   // RequestMappingHandlerMapping BeanDefinition注册
   readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
   
   // 省略部分代码...
   
   // RequestMappingHandlerAdapter
   RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
   // 省略属性赋值相关代码...
   // RequestMappingHandlerAdapter BeanDefinition注册
   readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);

   // 省略部分代码...

   // 进行默认组件的BeanDefinition注册
   // 涉及BeanNameUrlHandlerMapping、HttpRequestHandlerAdapter、
   // SimpleControllerHandlerAdapter、HandlerMappingIntrospector、
   // AcceptHeaderLocaleResolver、FixedThemeResolver、
   // DefaultRequestToViewNameTranslator、SessionFlashMapManager
   MvcNamespaceUtils.registerDefaultComponents(context, source);
   // 省略部分代码...
   return null;
}

BeanDefinition注册完成后,需要进行Bean的实例及初始化,在这个过程中完成了如url请求路径与具体Controller的RequestMapping方法的对应关系解析等操作。接下来针对核心的HandlerMapping、HandlerAdapter涉及的Bean实例及初始化过程中涉及的配置解析过程进行分析。

4.2.1.2 HandlerMapping

HandlerMapping对应的核心实现类为RequestMappingHandlerMapping,接下来通过这个类对应Bean的生成来分析请求URL与具体Controller的RequestMapping方法映射解析逻辑的分析。
RequestMappingHandlerMapping的UML类图如下所示:
UML类图-RequestMappingHandlerMapping
通过此图可以看到,它实现了InitializingBean接口,因此在Bean的创建过程中的初始化步骤会回调其实现的afterPropertiesSet()方法,以完成Bean初始化,RequestMappingHandlerMapping通过此方法完成了请求URL与具体Controller的RequestMapping方法映射解析逻辑,其UML时序图如下所示:
UML时序图-RequestMapping解析逻辑
从时序图可以看到,完成映射逻辑构建的核心方法为AbstractHandlerMethodMapping类的detectHandlerMethods()方法,其核心源码如下:

protected void detectHandlerMethods(Object handler) {
    // 获取BeanName对应的类类型
   Class<?> handlerType = (handler instanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());

   if (handlerType != null) {
       // 获取实际对应的类,此处可能会涉及到代理,所以需要获得被代理类
      Class<?> userType = ClassUtils.getUserClass(handlerType);
      // 获取类及其父类、接口方法上有RequestMapping注解的方法及RequestMappingInfo信息对应关系
      Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            (MethodIntrospector.MetadataLookup<T>) method -> {
               try {
                   // 获取方法上有RequestMapping注解的RequestMappingInfo信息
                  return getMappingForMethod(method, userType);
               }
               catch (Throwable ex) {
                  throw new IllegalStateException("Invalid mapping on handler class [" +
                        userType.getName() + "]: " + method, ex);
               }
            });
      // 省略日志输出代码...
      // 将类方法上有RequestMapping注解的方法及RequestMappingInfo信息对应关系注册到MappingRegistry中
      methods.forEach((method, mapping) -> {
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
         registerHandlerMethod(handler, invocableMethod, mapping);
      });
   }
}

其中,涉及到解析方法上@RequestMapping注解的方法getMappingForMethod(),其实现类为RequestMappingHandlerMapping
最终解析得到的RequestMapping注解标记的方法及其RequestMappingInfo信息注册到了MappingRegistryregistry属性中,该属性的类型为Map<T, MappingRegistration>,对应key为RequestMappingInfo信息。

4.2.1.3 HandlerAdapter

HandlerAdapter对应的常用核心实现类为RequestMappingHandlerAdapter,接下来通过这个类对应Bean的生成来分析请求的参数、返回值解析逻辑是如何进行初始化的。
RequestMappingHandlerAdapter的UML类图如下所示:
UML类图-RequestMappingHandlerAdapter
通过此图可以看到,它实现了InitializingBean接口,因此在Bean的创建过程中的初始化步骤会回调其实现的afterPropertiesSet()方法,以完成Bean初始化。
核心源码如下:

public void afterPropertiesSet() {
   // 初始化@ControllerAdvice注解标记的Controller切面Bean(非AOP的切面Bean)缓存
   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);
   }
}

从源码可以看到,主要完成了默认参数解析器、默认返回值处理器的设置。
参数解析器(HandlerMethodArgumentResolver)
负责将HTTP请求中的数据转换成处理器方法(Controller中的方法)所需的参数类型。它能够解析请求中的查询字符串、表单数据、路径变量、请求体等,并将其转换为Java对象。
如下示例所示:

@GetMapping("/goods/{id}")
public String greet(@PathVariable String id, @RequestParam String name) {
    return id + name;
}

这里的@PathVariable和@RequestParam就是通过参数解析器来解析路径变量和查询参数的。
返回值处理器(HandlerMethodReturnValueHandler)
负责处理控制器方法的返回值,并将其转换为合适的HTTP响应。这包括将返回的对象序列化为响应体(例如JSON或XML),或者决定视图名称以渲染HTML页面。
如下示例所示:

@GetMapping("/goods")
public Goods getUser(@RequestParam("id") int id) {
    return goodsRepo.findById(id);
}

这里如果没有显式地指定如何处理返回值,Spring MVC会自动使用一个适合的返回值处理器来将Goods对象转换为JSON格式并发送给客户端。

4.2.2 DispatcherServlet初始化

在前面WebApplicationContext容器(Spring容器)初始化过程中,添加了一个应用消息监听器ContextRefreshListener,用于监听Spring容器初始化完成事件。
当Spring容器初始化完成时,会发送ContextRefreshedEvent消息,ContextRefreshListener接收到消息后,调用FrameworkServlet#onApplicationEvent()方法,该方法内部最终调用的是DispatcherServlet#onRefresh()方法,进行HandlerMapping、HandlerAdapter、ViewResolver等与DispatcherServlet的绑定。
ContextRefreshListener源码如下:

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
   // 接收容器启动事件
   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
      FrameworkServlet.this.onApplicationEvent(event);
   }
}

DispatcherServlet#onRefresh()相关源码如下:

protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}
// 初始化DispatcherServlet需要用到的HandlerMapping、HandlerAdapter、ViewResolver等内容
protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

5.Spring MVC请求处理流程

由于Spring MVC是基于servlet API实现的,因此请求经过tomcat都会转发到DispatcherServlet上,以Get请求为例(其余请求类型大流程一致)其UML时序图如下所示:
UML时序图-请求处理流程
从上图可以看到,针对请求最核心的解析、处理逻辑为DispatcherServlet的doDispatch()方法,接下来将针对此方法结合Spring MVC的核心流程进行分析。

5.1 doDispatch()

该方法涉及的核心步骤有七步:

  1. 根据请求URL等信息从HandlerMapping中找到对应的HandlerExecutionChain;
  2. 根据HandlerExecutionChain中的Handler找到对应的HandlerAdapter;
  3. 执行前置拦截器HandlerInterceptor;
  4. 执行具体的业务逻辑Handler;
  5. 执行后置拦截器HandlerInterceptor;
  6. 处理返回结果;
  7. 执行任务执行完成拦截器HandlerInterceptor。

其核心源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
         // 检查是否为分段请求
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // 1.根据请求URL等信息从HandlerMapping中找到对应的HandlerExecutionChain
         mappedHandler = getHandler(processedRequest);
         // 省略部分代码...

         // 2.根据HandlerExecutionChain中的Handler找到对应的HandlerAdapter
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // 省略部分代码...
         
         // 3.执行前置拦截器HandlerInterceptor
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // 4.执行具体的业务逻辑Handler
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         // 省略部分代码...

         applyDefaultViewName(processedRequest, mv);
         // 5.执行后置拦截器HandlerInterceptor
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      // 省略部分代码...
      
      // 6.处理返回结果
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
       // 7.执行任务执行完成拦截器HandlerInterceptor
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   // 省略部分代码...
}

接下来针对其中的重点步骤进行详细分析。

5.2 HandlerMapping原理

HandlerMapping负责将客户端的 HTTP 请求映射到相应的处理器(Handler),作为用户请求与应用处理器之间的桥梁,为Spring MVC提供了灵活管理不同类型请求与处理器之间的映射关系。
我们以最常用的核心实现类RequestMappingHandlerMapping为示例进行分析,其UML时序图如下所示:
UML时序图-RequestMappingHandlerMapping处理请求逻辑
从时序图中,可以看到最核心的步骤为步骤7、步骤10,接下来对其源码进行分析。

5.2.1 lookupHandlerMethod()

该方法核心是根据请求信息,从预先扫描注册好的请求路径与处理器映射mappingRegistry中找到符合的HandlerMethod处理器,以下为相关匹配逻辑:

  1. 路径精确匹配:如果请求路径与映射路径完全一致,则这种映射具有最高的优先级。
  2. 路径变量数量:如果存在路径变量(如/user/{id}中的{id}),那么路径变量较少的映射可能被认为更加具体,因而优先级更高。
  3. 路径前缀长度:最长的路径前缀匹配可能会被优先考虑。
  4. HTTP方法:确保请求的HTTP方法(GET, POST等)与映射定义的方法相匹配。
  5. 消费和生产的内容类型:映射可能指定了处理器能处理的内容类型(如application/json),如果请求的内容类型与之匹配,则该映射可能优先。

其源码如下:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   // 先尝试直接根据请求路径查找请求映射信息RequestMappingInfo
   List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
   if (directPathMatches != null) {
      // 如果找了直接匹配的请求映射信息,则遍历直接匹配上的请求处理器映射信息,根据请求信息(请求方法、参数、URL等)构建符合的请求映射信息RequestMappingInfo
      // 进而构建出Match
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      // 如果没找到,则遍历注册的请求处理器映射信息,根据请求信息(请求方法、参数、URL等)构建符合的请求映射信息RequestMappingInfo
      // 进而构建出Match
      addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
   }
   if (!matches.isEmpty()) {
      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
          // 如果找到了多个匹配项,则使用MatchComparator,根据路径变量数量、路径前缀长度、消费和生产的内容类型等进行匹配,找到最佳匹配项
         Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
         matches.sort(comparator);
         bestMatch = matches.get(0);
         // 如果是预检请求(OPTION请求,用于检测是否运行跨域发送),且存在有跨域配置的Match,则返回空处理器
         if (CorsUtils.isPreFlightRequest(request)) {
             // 省略部分代码...
             // 查找是否有跨域配置的Match
         }
         else {
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
               // 省略部分代码...
               // 存在两个相同的最佳匹配项,则抛出异常
            }
         }
      }
      request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.getHandlerMethod();
   }
   // 如果未找到,则根据请求信息抛出相关异常
}

5.2.2 getHandlerExecutionChain()
此方法用于构建HandlerExecutionChain,主要目的是给对应HandlerMethod设置匹配的HandlerInterceptor拦截器,用于HandlerMethod执行前后进行拦截处理。其源码如下:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
   HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
         (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
   // 设置拦截器
   for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
      if (interceptor instanceof MappedInterceptor) {
         MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
         if (mappedInterceptor.matches(request)) {
            chain.addInterceptor(mappedInterceptor.getInterceptor());
         }
      }
      else {
         chain.addInterceptor(interceptor);
      }
   }
   return chain;
}

5.3 HandlerAdapter
HandlerAdapter 的作用主要是作为适配器,它负责将接收到的HTTP请求交给适当的控制器(Handler)处理。它是一个接口,定义了处理请求的标准方法。

具体来说,它的作用包括:
7. 适配不同的处理器:不同的控制器可能有不同的实现方式,HandlerAdapter 可以适配多种类型的处理器,使得Spring MVC能够支持多种不同的控制器实现。通过supports()方法,HandlerAdapter 可以判断自己是否支持某个特定的处理器。
8. 处理请求:通过 handle()方法,HandlerAdapter 负责调用处理器的方法来处理请求,并返回一个 ModelAndView 对象,该对象包含了视图名和模型数据。
9. 获取最后修改时间:可以通过 getLastModified()方法获取资源的最后修改时间,常用于于客户端请求缓存控制。

此接口常用的核心实现类为RequestMappingHandlerAdapter,接下来针对该类对于处理请求的逻辑进行分析,其核心源码如下:

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
      // 省略部分代码...
      ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
      if (this.argumentResolvers != null) {
          // 设置参数处理器
         invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
      }
      if (this.returnValueHandlers != null) {
          // 设置返回值处理器
         invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
      }
      // 省略部分代码...
      // 返回结果容器:暂存返回数据用
      ModelAndViewContainer mavContainer = new ModelAndViewContainer();
      // 省略部分代码...

      // 省略部分代码...
      // 处理请求:进行参数处理、业务逻辑调用、返回值处理
      invocableMethod.invokeAndHandle(webRequest, mavContainer);
      //省略部分代码...
      // 基于返回结果,构建ModelAndView
      return getModelAndView(mavContainer, modelFactory, webRequest);
   }
   finally {
      webRequest.requestCompleted();
   }
}

ServletInvocableHandlerMethod

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   // 使用反射的方式调用目标业务方法
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   setResponseStatus(webRequest);
   // 省略部分代码...
   try {
      // 对返回值做格式化等处理
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   // 省略部分代码...
}

5.4 HandlerInterceptor

HandlerInterceptor作为Spring MVC提供扩展接口,用于开发者在请求处理的前后执行一些操作,例如权限校验、日志记录等。

其核心方法有三个,分别在请求处理的不同阶段调用:

  1. preHandle():在请求处理之前调用。返回true表示继续流程(如调用下一个拦截器或处理器),返回false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器。
  2. postHandle():在请求处理之后、视图渲染之前调用。此方法可以对ModelAndView对象进行操作。
  3. afterCompletion():在整个请求完成,即视图渲染结束后调用。主要作用是用于清理资源等。
    2.postHandle():在请求处理之后、视图渲染之前调用。此方法可以对ModelAndView对象进行操作。

以下为实现示例,用于检查用户的登录状态:

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 检查session中是否存在用户信息
        if (request.getSession().getAttribute("user") != null) {
            // 用户已登录,放行
            return true;
        } else {
            // 用户未登录,重定向到登录页面
            response.sendRedirect(request.getContextPath() + "/login");
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 可以在这里对ModelAndView进行操作
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求完成后的操作
    }
}

6.总结

Spring MVC作为Spring框架的一部分,遵循MVC设计模式,用于提供Web框架功能。基于Servlet API的核心实现为DispatcherServlet,作为前端控制器,用于分发前端请求。内部通过HandlerMapping、HandlerAdapter、ViewResolver,完成了请求解析映射、请求业务逻辑调用、请求返回值处理解析。并且还通过HandlerInterceptor,提供了请求业务逻辑调用前、后、完成(视图渲染完成)三个阶段的扩展口,便于程序员自定义请求业务逻辑的执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值