了解 SpringMVC 请求流程

news/2024/12/23 18:38:56/

文章目录

  • 1. Spring 基础 - SpringMVC 请求流程
    • 1.1 引入
    • 1.2 什么是 MVC
    • 1.3 什么是 Spring MVC
    • 1.4 请求流程
      • 核心架构的具体流程步骤
      • 补充
    • 1.5 案例
      • **Maven 包引入**
      • **业务代码的编写**
      • Dao
      • Service
      • Controller
      • webapp 下的 web.xml
      • springmvc.xml
      • JSP 视图
  • 2. Spring 进阶 - DispatcherServlet 的初始化过程
    • 2.1 DispatcherServlet 和 ApplicationContext 有何关系?
    • 2.2 DispatcherServlet 是如何初始化的?
      • init
      • initWebApplicationContext
      • refresh
      • initHandlerxxx
  • 3. Spring 进阶 - DispatcherServlet 处理请求过程
    • 3.1 doGet 入口
    • 3.2 请求分发
    • 3.3 映射和适配器处理
    • 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 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 {@Autowiredprivate UserDaoImpl userDao;public List<User> findUserList() {return userDao.findUserList();}
}

Controller

java">@Controller
public class UserController {@Autowiredprivate 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.jspreturn modelAndView;}
}

webapp 下的 web.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>

springmvcxml_302">springmvc.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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://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

<%@ 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.xmlString configLocation = this.getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}// 初始化 Spring 环境this.configureAndRefreshWebApplicationContext(wac);return wac;}
}

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

java">protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {// 设置 context IDif (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,listenerwac.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());// 初始化 contextthis.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 {// 重置 contextthis.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 {// 判断是不是文件上传类型的 requestprocessedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 根据 request 获取匹配的 handlermappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}// 根据 handler 获取匹配的 handlerAdapterHandlerAdapter 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 处理,并返回 modelAndViewmv = 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 返回响应给用户。


http://www.ppmy.cn/news/1557538.html

相关文章

39.在 Vue3 中使用 OpenLayers 导出 GeoJSON 文件及详解 GEOJSON 格式

一、引言 在 Web 地图开发领域&#xff0c;Vue3 作为一款流行的前端框架&#xff0c;结合强大的 OpenLayers 地图库&#xff0c;能够实现丰富多样的地图功能。其中&#xff0c;将地图数据以 GeoJSON 格式导出是一项常见且实用的需求&#xff0c;本文将深入探讨如何在 Vue3 环境…

【day11】面向对象编程进阶(继承)

概述 本文深入探讨面向对象编程的核心概念&#xff0c;包括继承、方法重写、this和super关键字的使用&#xff0c;以及抽象类和方法的定义与实现。通过本文的学习&#xff0c;你将能够&#xff1a; 理解继承的优势。掌握继承的使用方法。了解继承后成员变量和成员方法的访问特…

iPhone恢复技巧:如何从 iPhone 恢复丢失的照片

在计算机时代&#xff0c;我们依靠手机来捕捉和存储珍贵的回忆。但是&#xff0c;如果您不小心删除或丢失了手机上的照片怎么办&#xff1f;这真的很令人沮丧和烦恼&#xff0c;不是吗&#xff1f;好吧&#xff0c;如果您在 iPhone 上丢失了照片&#xff0c;您不必担心&#xf…

大模型技术优化负载均衡:AI驱动的智能化运维

在现代信息技术环境中&#xff0c;负载均衡是确保系统稳定、高效运行的关键技术。随着大模型技术&#xff08;Large Model Technology, LMT&#xff09;的发展&#xff0c;AI驱动的智能化负载均衡成为了优化系统性能、提升用户体验的重要手段。本文将详细介绍如何使用Python实现…

CSS系列(22)-- 容器查询详解

前端技术探索系列&#xff1a;CSS 容器查询详解 &#x1f4e6; 致读者&#xff1a;探索组件级响应式设计 &#x1f44b; 前端开发者们&#xff0c; 今天我们将深入探讨 CSS 容器查询&#xff08;Container Queries&#xff09;&#xff0c;这项新特性让我们能够基于容器大小…

阿里开源最强数字人工具 EchoMimicV2,本地部署(一)

背景 EchoMimicV2是阿里推出的半身人体AI数字人项目,基于参考图片、音频剪辑和手部姿势序列生成高质量动画视频,确保音频内容与半身动作的一致性。现在本地部署,安装体验一下。 下载代码 git clone GitHub - antgroup/echomimic_v2: EchoMimicV2: Towa…

workman服务端开发模式-应用开发-gateway长链接端工作原理

一、长链接的工作原理 Register类其实也是基于基础的Worker开发的。Gateway进程和BusinessWorker进程启动后分别向Register进程注册自己的通讯地址&#xff0c;Gateway进程和BusinessWorker通过Register进程得到通讯地址后&#xff0c;就可以建立起连接并通讯了。而Gateway进程…

CMD使用SSH登陆Ubuntu

1.确认sshserver是否安装好 ps -e | grep sshd 450 ? 00:00:00 sshd 2、如果看到sshd那说明ssh-server已经启动了 其实在/etc/ssh下有一个sshd_config 文件。对这个文件进行修改vim sshd_config。 往文件中添加如下内容&#xff1a; Port 22 Protocol 2 PermitRootLogin yes P…