文章目录
- 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 处理请求的流程:
核心架构的具体流程步骤如下:
- 首先用户发送请求 ——> DispatcherSevlet,前端控制器受到请求后自己不进行处理,而是委托给其它的解析器进行处理,作为统一访问点,进行全局的流程控制;
- DispatcherServlet ——> HandlerMapping,HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)、多个 HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
- DispatcherServlet ——> HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
- HandlerAdapter ——> 处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名);
- ModelAndView 的逻辑视图名 ——> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的 View,通过这种策略模式,很容易更换其它视图技术;
- View ——> 渲染,View 会根据传进来的 Model 模型数据进行渲染,此处的 Model 实际是一个 Map 数据结构,因此很容易支持其它视图技术;
- 返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户,至此一个流程结束。
补充
上述流程仅为核心流程,这里补充一些其它组件:
- Filter(ServletFilter)
进入 Servlet 前可以有 preFilter,Servlet 处理之后还可有 postFilter。
- LocaleResolver
在视图解析/渲染时,还需要考虑国际化(Local),显然这里需要有 LocalResolver。
- ThemeResolver
如何控制视图样式呢?SpringMVC 中还涉及了 ThemeSource 接口和 ThemeResolver,包含一些静态资源的集合(样式及图片等),用来控制应用的视觉风格。
- 对于文件的上传请求?
对于常规请求上述流程是合理的,但是如果是文件的上传请求,那么就不太一样了;所以这里便出现了 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 的请求流程:
- HandlerMapping 是映射处理器
- HandlerAdapter 是处理器适配器,它用来找到你的 Controller 中的处理方法
- 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 返回响应给用户。