了解 SpringMVC 请求流程

文章目录

  • [1. Spring 基础 - SpringMVC 请求流程](#1. Spring 基础 - SpringMVC 请求流程)
  • [2. Spring 进阶 - DispatcherServlet 的初始化过程](#2. Spring 进阶 - DispatcherServlet 的初始化过程)
    • [2.1 DispatcherServlet 和 ApplicationContext 有何关系?](#2.1 DispatcherServlet 和 ApplicationContext 有何关系?)
    • [2.2 DispatcherServlet 是如何初始化的?](#2.2 DispatcherServlet 是如何初始化的?)
  • [3. Spring 进阶 - DispatcherServlet 处理请求过程](#3. Spring 进阶 - DispatcherServlet 处理请求过程)
    • [3.1 doGet 入口](#3.1 doGet 入口)
    • [3.2 请求分发](#3.2 请求分发)
    • [3.3 映射和适配器处理](#3.3 映射和适配器处理)
    • [3.4 视图渲染](#3.4 视图渲染)

本文围绕 SpringMVC 请求流程进行展开,帮助进一步理解 SpringMVC。

1. Spring 基础 - SpringMVC 请求流程

前边我们学习了 Spring 框架和 Spring 框架中最为重要的两个技术点(IOP 和 AOP),那么如何更好的构建上层的应用呢(比如 web 应用),这便是 SpringMVC;Spring MVC 是 Spring 在 Spring Container Core 和 AOP 等技术基础上,遵循上述 Web MVC 的规范推出的 web 开发框架,目的是为了简化 Java 栈的 web 开发。本节主要围绕 SpringMVC 主要的流程,并编写基础案例。

1.1 引入

前边我们学习了 Spring 框架和 Spring 框架中最为重要的两个技术点(IOC 和 AOP)。

​ 那么,如何更好的构建上层的应用呢?比如 web 应用?

​ 针对上层的 Web 应用,SpringMVC 诞生了,它也是 Spring 技术栈中最为重要的一个框架。

通过如下问题构建对 SpringMVC 的认识。

  • Java 技术栈的 Web 应用是如何发展的?
  • 什么是 MVC,什么是 SpringMVC?
  • SpringMVC 主要的请求流程是什么样的?
  • SpringMVC 中还有哪些组件?
  • 如何编写一个简单的 SpringMVC 程序呢?

1.2 什么是 MVC

MVC 英文是 Model View Controller,是模型(model)- 视图(view)- 控制器(controller)的缩写,一种软件设计规范。本质上也是一种解耦。

​ 用一种业务逻辑、数据、界面显示分离的方法,将业务逻辑聚集到一个部件里面,在改进和个性化定制解码及用户交互的同时,不需要重新编写业务逻辑。MVC 被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
  • View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
  • Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从读取数据,控制用户输入,并向模型发送数据。

1.3 什么是 Spring MVC

简单而言,Spring MVC 是 Spring 在 Spring Container Core 和 AOP 等技术基础上,遵循上述 Web MVC 的规范推出的 web 开发框架,目的是为了简化 Java 栈的 web 开发。

​ Spring Web MVC 是一种基于 Java 的实现了 Web MVC 设计模式的请求驱动类型的轻量级 Web 框架,即使用了 MVC 框架模式的思想,将 web 层进行职责解耦,基于请求驱动指的是使用请求 - 响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC 也是要简化我们日常 Web 开发的。

相关特征如下

  • 让我们能非常简单的设计出干净的 Web 层和薄薄的 Web 层;
  • 进行更简介的 Web 层的开发;
  • 天生与 Spring 框架继承(如 IoC 容器、AOP 等);
  • 提供强大的约定大于配置的契约式编程支持;
  • 能简单的进行 Web 层的单元测试;
  • 支持灵活的 URL 到页面控制器的映射;
  • 非常容易与其它视图技术集成,如 Velocity、FreeMarker 等等,因为模型数据不放在特定的 API 里,而是放在一个 Model 里(Map 数据结构实现,因此很容易被其它框架使用);
  • 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的 API;
  • 提供一套强大的 JSP 标签库,简化 JSP 开发;
  • 支持灵活的本地化、主体等解析;
  • 更加简单的异常处理;
  • 对静态资源的支持;
  • 支持 Restful 风格。

1.4 请求流程

Spring Web MVC 框架也是一个基于请求驱动的 Web 框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理。

核心架构的具体流程步骤

首先让我们整体看一下 Spring Web MVC 处理请求的流程:

核心架构的具体流程步骤如下:

  1. 首先用户发送请求 ------> DispatcherSevlet,前端控制器受到请求后自己不进行处理,而是委托给其它的解析器进行处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet ------> HandlerMapping,HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)、多个 HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
  3. DispatcherServlet ------> HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter ------> 处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名);
  5. ModelAndView 的逻辑视图名 ------> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的 View,通过这种策略模式,很容易更换其它视图技术;
  6. View ------> 渲染,View 会根据传进来的 Model 模型数据进行渲染,此处的 Model 实际是一个 Map 数据结构,因此很容易支持其它视图技术;
  7. 返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户,至此一个流程结束。

补充

上述流程仅为核心流程,这里补充一些其它组件:

  1. Filter(ServletFilter)

​ 进入 Servlet 前可以有 preFilter,Servlet 处理之后还可有 postFilter。

  1. LocaleResolver

​ 在视图解析/渲染时,还需要考虑国际化(Local),显然这里需要有 LocalResolver。

  1. ThemeResolver

​ 如何控制视图样式呢?SpringMVC 中还涉及了 ThemeSource 接口和 ThemeResolver,包含一些静态资源的集合(样式及图片等),用来控制应用的视觉风格。

  1. 对于文件的上传请求?

​ 对于常规请求上述流程是合理的,但是如果是文件的上传请求,那么就不太一样了;所以这里便出现了 MultipartResolver。

1.5 案例

Maven 包引入

​ 主要引入 spring-webmvc 包(spring-webmvc 包中已经包含了 Spring Core Container 相关的包),以及 servlet 和 jstl(JSP 中使用 jstl)的包。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.zhanbo</groupId>
    <artifactId>Spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>8</java.version>
        <spring.version>5.3.9</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

业务代码的编写

​ User 实体

java 复制代码
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Dao

java 复制代码
@Repository
public class UserDaoImpl {
    public List<User> findUserList() {
        return Collections.singletonList(new User("zhanbo", 18));
    }
}

Service

java 复制代码
@Service
public class UserServiceImpl {
    
    @Autowired
    private UserDaoImpl userDao;
    
    public List<User> findUserList() {
        return userDao.findUserList();
    }
}

Controller

java 复制代码
@Controller
public class UserController {

    @Autowired
    private UserServiceImpl userService;

    @RequestMapping("/user")
    public ModelAndView list(HttpServletRequest request, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("dateTime", new Date());
        modelAndView.addObject("userList", userService.findUserList());
        modelAndView.setViewName("userList"); // views 目录下 userList.jsp
        return modelAndView;
    }
}

webapp 下的 web.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>SpringMVC</display-name>
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 通过初始化参数指定 SpringMVC 配置文件的位置和名称 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

springmvc.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<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.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 扫描注解 -->
    <context:component-scan base-package="io.zhanbo.spring.springmvc"/>

    <!-- 静态资源处理 -->
    <mvc:default-servlet-handler/>

    <!-- 开启注解 -->
    <mvc:annotation-driven/>
        
    <!-- 视图解析器 -->
    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

JSP 视图

​ 创建 userList.jsp

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>User List</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <c:if test="${!empty userList}">
            <table class="table table-bordered table-striped">
                <tr>
                    <th>Name</th>
                    <th>Age</th>
                </tr>
                <c:forEach items="${userList}" var="user">
                    <tr>
                        <td>${user.name}</td>
                        <td>${user.age}</td>
                    </tr>
                </c:forEach>
            </table>
        </c:if>
    </div>
</body>
</html>

2. Spring 进阶 - DispatcherServlet 的初始化过程

前面我们有了 IOC 的源码基础以及 SpringMVC 的基础,我们便可以进一步深入理解 SpringMVC 主要实现原理,包含 DispatcherServlet 的初始化过程和 DispatcherServlet 处理请求的过程的源码解析。本小节围绕 DispatcherServlet 的初始化火车的源码解析。

2.1 DispatcherServlet 和 ApplicationContext 有何关系?

DispatcherServlet 作为一个 Servlet,需要根据 Servlet 规范使用 Java 配置或 web.xml 声明和映射。反过来,DispatcherServlet 使用 Spring 配置来发现请求、视图解析、异常处理等等所需的委托组件。那它和 ApplicationContext 有和关系呢?

​ DispatcherServlet 需要 WebApplicationContext(继承自 ApplicationContext)来配置。WebApplicationContext 可以链接到 ServletContext 和 Servlet。因为绑定了 ServletContext,这样应用程序就可以在需要的时候使用 RequestContextUtils 的静态方法访问 WebApplicationContext。

​ 大多数应用程序只有一个 WebApplicationContext,除此之外也可以一个 Root WebApplicaitonContext 配多个 Servlet 实例,然后各自拥有自己的 Servlet WebApplicationContext 配置。

​ Root WebApplicationContext 包含需要共享给多个 Servlet 实例的数据源和业务服务基础 Bean。这些 Bean 可以在 Servlet 特定的范围被集成或覆盖。

2.2 DispatcherServlet 是如何初始化的?

DispatcherServlet 首先是 Servlet,Servlet 有自己的生命周期的方法(init,destory 等),那么我们在看 DispatcherServlet 初始化时首先需要看源码中 DispatcherServlet 的类结构设计。

​ 首先我们看 DispatcherServlet 的类结构关系,在这个类依赖结构中找到 init 的方法。

​ 很容易找到 init() 的方法位于 HttpServletBean 中。

init

​ init() 方法如下,主要读取 web.xml 中 servlet 参数配置,并交给子类方法 initServletBean() 继续初始化

java 复制代码
public final void init() throws ServletException {
    // 读取 web.xml 中的 Servlet 配置
    PropertyValues pvs = new ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            // 转换成 BeanWrapper,为了方便使用 Spring 的属性注入功能
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            // 注入 Resource 类型需要依赖于 ResourceEditor 解析,所以注册 Resource 类关联到 ResourceEditor 解析器
            ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
            // 更多的初始化可以让子类进行拓展
            this.initBeanWrapper(bw);
            // 让 spring 注入 namespace,contextConfigLocation 等属性
            bw.setPropertyValues(pvs, true);
        } catch (BeansException ex) {
            if (this.logger.isErrorEnabled()) {
                this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", ex);
            }

            throw ex;
        }
    }
	// 让子类进行拓展
    this.initServletBean();
}

​ 通过断点,我们可以知道读取配置,正是初始化了我们 web.xml 中配置。

​ 再看下 initServletBean() 方法,位于 FrameworkServlet 类中

java 复制代码
protected final void initServletBean() throws ServletException {
    this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
    }

    long startTime = System.currentTimeMillis();

    try {
        // 最重要的是这个方法
        this.webApplicationContext = this.initWebApplicationContext();
        // 可以让子类进一步拓展
        this.initFrameworkServlet();
    } catch (RuntimeException | ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isDebugEnabled()) {
        String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
        this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
    }

    if (this.logger.isInfoEnabled()) {
        this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }

}

initWebApplicationContext

​ initWebApplicationContext 用来初始化和刷新 WebApplicationContext。

​ 方法如下:

java 复制代码
protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    WebApplicationContext wac = null;
    // 如果在构建函数已经被初始化
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }

                this.configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
	// 没有在构建函数中初始化,则尝试通过 contextAttribute 初始化
    if (wac == null) {
        wac = this.findWebApplicationContext();
    }
	// 还没有的话,只能重新创建了
    if (wac == null) {
        wac = this.createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        synchronized(this.onRefreshMonitor) {
            this.onRefresh(wac);
        }
    }

    if (this.publishContext) {
        String attrName = this.getServletContextAttributeName();
        this.getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

​ webApplicationContext 只会初始化一次,依次尝试构建函数初始化,没有则通过 contextAttribute 初始化,仍没有则会创建新的。

​ createWebApplicationContext 方法如下:

java 复制代码
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    Class<?> contextClass = this.getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
    } else {
        // 通过反射方式初始化
        ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        wac.setEnvironment(this.getEnvironment());
        wac.setParent(parent);
        // 这里便是 springmvc.xml
        String configLocation = this.getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
		// 初始化 Spring 环境
        this.configureAndRefreshWebApplicationContext(wac);
        return wac;
    }
}

​ configAndRefreshWebApplicationContext 方法初始化设置 Spring 环境

java 复制代码
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // 设置 context ID
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        if (this.contextId != null) {
            wac.setId(this.contextId);
        } else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());
        }
    }
	// 设置 servletContext,servletConfig,namespace,listener
    wac.setServletContext(this.getServletContext());
    wac.setServletConfig(this.getServletConfig());
    wac.setNamespace(this.getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());
    }
	// 让子类去拓展
    this.postProcessWebApplicationContext(wac);
    this.applyInitializers(wac);
    // Spring 环境初始化完了,就可以初始化 DispatcherServlet 处理流程中需要的组件了
    wac.refresh();
}

refresh

​ 有了 webApplicationContext 后,就开始刷新了(onRefresh 方法),这个方法是 FrameworkServlet 提供的模板方法,由子类 DispatcherServlet 来实现的。

java 复制代码
protected void onRefresh(ApplicationContext context) {
    this.initStrategies(context);
}

​ 刷新主要是调用 initStrategies(context) 方法对 DispatcherServlet 中的组件进行初始化,这些组件就是在 SpringMVC 请求流程中包的主要组件。

java 复制代码
protected void initStrategies(ApplicationContext context) {
    this.initMultipartResolver(context);
    this.initLocaleResolver(context);
    this.initThemeResolver(context);
    // 主要看如下三个方法
    this.initHandlerMappings(context);
    this.initHandlerAdapters(context);
    this.initHandlerExceptionResolvers(context);
    this.initRequestToViewNameTranslator(context);
    this.initViewResolvers(context);
    this.initFlashMapManager(context);
}

initHandlerxxx

​ 我们主要看 initHandlerXXX 相关的方法,它们之间的关系可以看 SpringMVC 的请求流程:

  1. HandlerMapping 是映射处理器
  2. HandlerAdapter 是处理器适配器,它用来找到你的 Controller 中的处理方法
  3. HandlerExceptionResolver 是当遇到处理异常时的异常解析器

​ initHandlerMapping 方法如下,无非就是获取按照优先级排序后的 HandlerMappings,将来匹配时按优先级高的 HandlerMapping 进行处理。

java 复制代码
 private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException var4) {
        }
    }

    if (this.handlerMappings == null) {
        this.handlerMappings = this.<HandlerMapping>getDefaultStrategies(context, HandlerMapping.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
        }
    }

    for(HandlerMapping mapping : this.handlerMappings) {
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }

}

​ initHandlerAdapters 方法和 initHandlerExceptionResolvers 方法也是类似的,如果没有找到,那就构建默认的。

java 复制代码
private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;
    if (this.detectAllHandlerAdapters) {
        Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    } else {
        try {
            HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        } catch (NoSuchBeanDefinitionException var3) {
        }
    }

    if (this.handlerAdapters == null) {
        this.handlerAdapters = this.<HandlerAdapter>getDefaultStrategies(context, HandlerAdapter.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No HandlerAdapters declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
        }
    }

}

private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;
    if (this.detectAllHandlerExceptionResolvers) {
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    } else {
        try {
            HandlerExceptionResolver her = (HandlerExceptionResolver)context.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        } catch (NoSuchBeanDefinitionException var3) {
        }
    }

    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = this.<HandlerExceptionResolver>getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No HandlerExceptionResolvers declared in servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
        }
    }

}

3. Spring 进阶 - DispatcherServlet 处理请求过程

本节围绕 DispatcherServlet 处理请求过程的源码进行解析。

3.1 doGet 入口

以 1.5 节案例为例,通过 Maven Plugn 运行 tomcat,当请求 URL 是 http://localhost:8080/user

​ 我们知道 Servlet 处理 get 请求是 doGet 方法,所以我们去找 DispatcherServlet 类结构中的 doGet 方法。

java 复制代码
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processRequest(request, response);
}

​ processRequest 处理请求的方法如下:

java 复制代码
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 计算处理请求的时间
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = this.buildLocaleContext(request);
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    // 初始化 context
    this.initContextHolders(request, localeContext, requestAttributes);

    try {
        // 这里处理
        this.doService(request, response);
    } catch (IOException | ServletException ex) {
        failureCause = ex;
        throw ex;
    } catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    } finally {
        // 重置 context
        this.resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }

        this.logResult(request, response, failureCause, asyncManager);
        this.publishRequestHandledEvent(request, response, startTime, failureCause);
    }

}

​ 本质上就是调用 doService 方法,由 DispatcherServlet 类实现。

java 复制代码
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    this.logRequest(request);
    // 保存下请求之前的参数
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap();
        Enumeration<?> attrNames = request.getAttributeNames();

        while(attrNames.hasMoreElements()) {
            String attrName = (String)attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }
	
    // 方便后续 handlers 和 view 要使用它们
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath previousRequestPath = null;
    if (this.parseRequestPath) {
        previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
        ServletRequestPathUtils.parseAndCache(request);
    }

    try {
        // 请求分发
        this.doDispatch(request, response);
    } finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
            this.restoreAttributesAfterInclude(request, attributesSnapshot);
        }

        if (this.parseRequestPath) {
            ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
        }

    }

}

3.2 请求分发

​ doDispatch 方法是真正处理请求的核心方法:

java 复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

            try {
                // 判断是不是文件上传类型的 request
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                // 根据 request 获取匹配的 handler
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
				// 根据 handler 获取匹配的 handlerAdapter
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                // 如果 handler 支持 last-modified 头处理
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
				// 真正 handle 处理,并返回 modelAndView
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
				
                // 通过视图的 prefix 和 postfix 获取完整的视图名
                this.applyDefaultViewName(processedRequest, mv);
                // 应用后置的拦截器
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception ex) {
                dispatchException = ex;
            } catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
			// 处理 handler 处理的结果,显然就是对 ModelAndView 或者出现的 Exception 处理
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception ex) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Throwable err) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}

3.3 映射和适配器处理

​ 对于真正的 handle 方法,我们看下其处理流程:

java 复制代码
// AbstractHandlerMethodAdapter
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return this.handleInternal(request, response, (HandlerMethod)handler);
}

​ 交给 handlerInternal 方法处理,以 RequestMappingHandlerAdapter 这个 HandlerAdapter 中的处理方法为例:

java 复制代码
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    this.checkRequest(request);
    ModelAndView mav;
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized(mutex) {
                mav = this.invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            mav = this.invokeHandlerMethod(request, response, handlerMethod);
        }
    } else {
        mav = this.invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader("Cache-Control")) {
        if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        } else {
            this.prepareResponse(response);
        }
    }

    return mav;
}

​ 然后指向 invokeHandlerMethod 这个方法,用来对 RequestMapping(usercontroller 中的 list 方法)进行处理

java 复制代码
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ServletWebRequest webRequest = new ServletWebRequest(request, response);

    Object result;
    try {
        WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
        // 重要:设置 handler(controller#list) 方法上的参数,返回值处理,绑定 databinder 等
        ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }

        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }

        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
        if (asyncManager.hasConcurrentResult()) {
            result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }
		// 执行 controller 中方法
        invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
        if (!asyncManager.isConcurrentHandlingStarted()) {
            result = this.getModelAndView(mavContainer, modelFactory, webRequest);
            return (ModelAndView)result;
        }

        result = null;
    } finally {
        webRequest.requestCompleted();
    }

    return (ModelAndView)result;
}

​ invokeAndHandler 交给 UserController 中具体执行 list 方法执行。

后续 invoke 执行的方法,直接看整个请求流程的调用链即可。执行后获得视图和 Model。

3.4 视图渲染

​ 接下来继续执行 processDispatchResult 方法,对视图和 model(如果有异常则对异常处理)进行处理(显然就是渲染页面)。

java 复制代码
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    // 如果处理过程中有异常,则异常处理
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            this.logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException)exception).getModelAndView();
        } else {
            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
            mv = this.processHandlerException(request, response, handler, exception);
            errorView = mv != null;
        }
    }
	// 是否需要渲染视图
    if (mv != null && !mv.wasCleared()) {
        this.render(mv, request, response); // 渲染视图
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else if (this.logger.isTraceEnabled()) {
        this.logger.trace("No view rendering, null ModelAndView returned.");
    }

    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }

    }
}

​ 接下来显然就是渲染视图了,spring 在 initStrategies 方法中初始化的组件(LocaleResovler 等)就派上用场了。

java 复制代码
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
    response.setLocale(locale);
    String viewName = mv.getViewName();
    View view;
    if (viewName != null) {
        view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
        }
    } else {
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
        }
    }

    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Rendering view [" + view + "] ");
    }

    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }

        view.render(mv.getModelInternal(), request, response);
    } catch (Exception var8) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Error rendering view [" + view + "]", var8);
        }

        throw var8;
    }
}

​ 后续就是通过 viewResolver 进行解析,最后无非就是返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户。

相关推荐
Q_19284999066 分钟前
基于Spring Boot的小区车辆管理系统
java·spring boot·后端
Mr. zhihao11 分钟前
Spring Boot 条件注解:@ConditionalOnProperty 完全解析
java·spring boot·后端
豆本-豆豆奶15 分钟前
用python实现滑雪小游戏,附源码
开发语言·python·pygame
梧桐树042917 分钟前
python IO编程:序列化
开发语言·python
追逐梦想永不停21 分钟前
Java连接chatGPT步骤(免费key获取方法)
java·开发语言·chatgpt
知识分享小能手22 分钟前
Java学习教程,从入门到精通,Java LinkedList(链表)语法知识点及案例代码(62)
java·大数据·开发语言·学习·链表·intellij-idea·大数据开发
爱吃西瓜的小菜鸡22 分钟前
【C语言】贪心吃糖
c语言·开发语言·学习·算法
ybq1951334543122 分钟前
javaEE--计算机是如何工作的-1
java·前端·java-ee
暗夜绿27 分钟前
【GoF23种设计模式】02_单例模式(Singleton Pattern)
java·spring·单例模式·设计模式
White graces29 分钟前
掌握HTML, 从零开始的网页设计
开发语言·前端·windows·edge·html·visual studio code