文章目录

  • 1 介绍
  • 2 SpringMVC 快速体验
  • 3 ContextLoaderListener
  • 3.1 ServletContextListener 的使用
  • 3.2 Spring 中的 ContextLoaderListener
  • 4 DispatcherServlet
  • 4.1 Servlet 的使用
  • 4.2 DispatcherServlet 的初始化
  • 4.3 WebApplicationContext 的初始化
  • 5 DispatcherServlet 的逻辑处理
  • 5.1 MultipartContent 类型的 request 处理
  • 5.2 根据 request 信息寻找对应的 Handler
  • 5.3 没有找到对应的 Handler 的异常处理
  • 5.4 根据当前 Handler 寻找对应的 HandlerAdapter
  • 5.5 缓存处理
  • 5.6 HandlerInterceptor 的处理
  • 5.7 逻辑处理
  • 5.8 异常视图的处理
  • 5.9 根据视图跳转页面


1 介绍

Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。通过策略接口,Spring 框架是高度可配置的,而且支持多种视图技术,例如 JavaServer Pages(JSP)、Velocity、Tiles、IText 和 POI。SpringMVC 框架并不知道使用的视图。SpringMVC 分离了控制器、模型对象、分派以及处理程序对象的角色。

SpringMVC 基于 Servlet 功能实现的,通过实现 Servlet 接口的 DispatcherServlet 来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上传文件支持。默认的处理程序是简单的 Controller 接口,只有一个方法ModelAndView handleRequest(request, response)。Spring 提供了一个控制层次机构,可以派生子类。如果应用程序需要处理用户输入表单,那么可以继承 AbstractFormController。如果需要把多页输入处理到一个表单,那么可以继承 AbstractWizardFormController。

SpringMVC 或其他较成熟的 MVC 框架,解决的问题无外乎以下几点。

  1. 将 Web 页面的请求传给服务器。
  2. 根据不同的请求处理不同的逻辑单元。
  3. 返回处理结果数据并跳转至响应页面。

2 SpringMVC 快速体验

1)配置 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">

	<!-- 使用 ContextLoaaderListener 配置时需要指定 Spring 配置文件的位置-->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<!-- SpringMVC 前端控制器-->
	<!-- DispatcherServlet 载入后,它将从一个 XML 文件中载入 Spring 应用的上下文,该 XML 文件的名字取决于<servlet-name>-->
	<!-- 这里 DispatcherServlet 将试图从springmvc-servlet.xml 文件中载入应用试下文-->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
		<init-param>
			<param-name>detectAllHandlerMappings</param-name>
			<param-value>false</param-value>
		</init-param>
	</servlet>

	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>

	<!-- 配置上下文载入器-->
	<!-- 上下文载入器除 DispatcherServlet 载入的配置文件之外的其他上下文配置文件-->
	<!-- 最常用的是一个 Servlet 监听器 ContextLoaderListener-->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<listener>
		<listener-class>org.springframework.demo.web.MyDataContextListener</listener-class>
	</listener>
</web-app>

SpringMVC 需要 web.xml 文件原因是需要配置:

  1. contextConfigLocation:Spring 的核心是配置文件,这个参数是 Web 与 Spring 相结合的一个关键配置。
  2. DispatcherServlet:包含了 SpringMVC 的请求逻辑,Spring 使用此类拦截 Web 请求并进行相应的逻辑开发。

2)创建 Spring 配置文件 applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 该 bean 会在 ModelAndView 返回的视图名前加上 prefix前缀,在加上 suffix 后缀-->
	<bean  id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WBE-INF/jsp/" />
		<property name="suffix" value=".jsp" />
	</bean>
</beans>

InternalResourceViewResolver 该 bean 会在 ModelAndView 返回的视图名前加上 prefix 前缀,在加上 suffix 后缀。

3)创建 model

public class User {

	private int id;
	private String name;
	private int age;
	private String sex;
    
    // 省略 get/set 方法
}

model 即模型用来承载数据。

4)创建 controller
控制器用于处理 WEB 请求,每个控制器对于一个逻辑处理。

public class UserController extends AbstractController {

	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
		List<User> list = new ArrayList<>(2);
		User userA = new User(1, "张三", 12, "男");
		User userB = new User(2, "李四", 15, "女");
		list.add(userA);
		list.add(userB);
		return new ModelAndView("userlist", "users", list);
	}
}

ModelAndView 类在 SpringMVC 中占有很主要的地位,控制器执行方法都必须返回一个 ModelAndView,其保存了视图以及视图显示的模型数据。参数说明如下。

  1. 参数1 userlist:视图名称
  2. 参数2 users:模型名称
  3. 参数3 list:模型值

5)创建视图文件 userlist.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>测试数据</title>
</head>
<body>
    <c:forEach items="${users}" var="user">
        <c:out value="${user.id}" />
        <c:out value="${user.name}" />
    </c:forEach>
</body>
</html>

视图文件用于展现请求处理结果。

6)创建 Servlet 配置文件 spring-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<props>
				<prop key="/userlist.jsp">userController</prop>
			</props>
		</property>
	</bean>
	<!-- id 对应 映射器中的 mappings-->
	<bean id="userController" class="org.springframework.demo.web.UserController" />
</beans>

SpringMVC 是基于 Servlet 实现的,所以在启动的时候,服务器会首先加载对应于 Servlet 的配置文件。

3 ContextLoaderListener

当使用编程方式时,我们可以直接将配置文件信息作为参数传入 Spring 中。如

ApplicationContext bf = new ClassPathXmlApplicationContext("applicationContext.xml");

但是在 web 中我们需要与 web 环境相互结合,通常做法是将路径以context-param的方式注册并使用ContextLoaderListener监听读取。

ContextLoaderListener 的作用是启动 web 容器时,自动装配 ApplicationContext 配置信息。其实现了 ServletContextListener 这个接口,在启动容器时就会执行默认的方法,使用 ServletContextListener 接口,开发者能够在为客户端请求服务之前向 ServletContext 中添加任意对象。这个对象在 ServletContext 启动时被初始化,该 ServletContext 在整个运行期间都是可见的。每个 Web 应用都有一个 ServletContext 与其对应。

在 ContextLoaderListener 中的核心逻辑就是初始化 WebApplicationContext 实例并存放至 ServletContext 中。

3.1 ServletContextListener 的使用

首先了解下 ServletContextListener 的使用。
1)创建自定义 ServletContextListener

public class MyDataContextListener implements ServletContextListener {

	private ServletContext context = null;

	/**
	 * 该方法在容器启动后被调用
	 */
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		context = sce.getServletContext();
		context.setAttribute("myData", "this is my Data");
	}

	/** 在 ServletContext 即将关闭时调用*/
	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		context = null;
	}
}

2)注册监听器 web.xml

<listener>
	<listener-class>org.springframework.demo.web.MyDataContextListener</listener-class>
</listener>

3)测试
一旦 web 应用启动,我们就可以在任意 Servlet 或者 Jsp 中通过下述方式获取我们初始化的参数。

String myData = (String) getServletContext().getAttribute("myData");

3.2 Spring 中的 ContextLoaderListener

ServletContext 启动之后首先调用 ServletContextListener 的 contextInitialized 方法。

// ContextLoaderListener.java
public void contextInitialized(ServletContextEvent event) {
	// 初始化应用上下文 WebApplicationContext
	initWebApplicationContext(event.getServletContext());
}

这里使用了一个 WebApplicationContext:在 Web 应用中,我们会使用到 WebApplicationContext,其继承自 ApplicationContext,在 ApplicationContext 的基础上追加了一些特定与 Web 的操作。

// ContextLoader.java
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	// 如果 web.xml 中存咋多次 ContextLoader 的定义
	// WebApplicationContext 存在性校验
	// 在配置中只允许声明一次 ServletContextListener。在 Spring 中如果创建 WebApplicationContext 实例会记录在
	// ServletContext 中以便全局调用,使用的 key 就是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException(
				"Cannot initialize context because there is already a root application context present - " +
				"check whether you have multiple ContextLoader* definitions in your web.xml!");
	}

	servletContext.log("Initializing Spring root WebApplicationContext");
	Log logger = LogFactory.getLog(ContextLoader.class);
	if (logger.isInfoEnabled()) {
		logger.info("Root WebApplicationContext: initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		// Store context in local instance variable, to guarantee that
		// it is available on ServletContext shutdown.
		if (this.context == null) {
			// 初始化 context - WebApplicationContext
			// 创建 WebApplicationContext 实例委托给 createWebApplicationContext 函数
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent ->
					// determine parent for root web application context, if any.
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				// 配置刷新环境
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		// 设置缓存,将实例记录在 ServletContext 中
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		}
		else if (ccl != null) {
			// 映射当前的类加载器与创建的实例到全局变量 currentContextPerThread 中
			currentContextPerThread.put(ccl, this.context);
		}

		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
		}

		return this.context;
	}
	catch (RuntimeException | Error ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

initWebApplicationContext 中体现创建 WebApplicationContext 的大致步骤,主要逻辑如下:
1)WebApplicationContext 存在性校验
在配置中只允许声明一次 ServletContextListener。在 Spring 中如果创建 WebApplicationContext 实例会记录在 ServletContext 中以便全局调用,使用的 key 就是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

2)创建 WebApplicationContext 实例
实例委托给 createWebApplicationContext 函数实现。

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	// 获取 Class 类型
	Class<?> contextClass = determineContextClass(sc);
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
				"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
	}
	// 进行实例化
	return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
	// 从上下文中获取 className - CONTEXT_CLASS_PARAM = contextClass
	String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
	// 如果配置了
	if (contextClassName != null) {
		try {
			// 反射获取 Class
			return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException(
					"Failed to load custom context class [" + contextClassName + "]", ex);
		}
	}
	else {
		// 如果没有配置,则从默认参数中获取,从 ContextLoader.properties 属性文件中获取
		contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
		try {
			return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException(
					"Failed to load default context class [" + contextClassName + "]", ex);
		}
	}
}

其中在 ContextLoader 中有这样的静态代码块。

static {
	// Load default strategy implementations from properties file.
	// This is currently strictly internal and not meant to be customized
	// by application developers.
	try {
		// DEFAULT_STRATEGIES_PATH = ContextLoader.properties
		// 加载配置资源,根据该代码,可以知道在读取类 ContextLoader 路径下一定会有 ContextLoader.properties 这个文件
		ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
		throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
	}
}

根据该代码,可以知道在读取类 ContextLoader 路径下一定会有 ContextLoader.properties。查看内容如下:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

根据上述代码分析,在初始化过程中,程序会首先读取 ContextLoader 类的同目录下的属性文件 ContextLoader.properties,并根据其中的配置提取将要实现的 WebApplicationContext 接口的实现类,并提供反射进行实例的创建。

3)将实例记录在 ServletContext 中。

4)映射当前的类加载器与创建的实例到全局变量 currentContextPerThread 中。

4 DispatcherServlet

在 Spring 中,ContextLoaderListener 只是用于创建 WebApplicationContext 的实例,真正的逻辑是在 DispatcherServlet 中实现的。DispatcherServlet 是实现 servlet 接口的实现类。

Servlet 是一个 Java 编写的程序,此程序是基于 HTTP 协议编写的一个 Java 类。主要是处理客户端的请求并将其结果发送到客户端。Servlet 的生命周期是由 Servlet 的容器来控制的,它分为初始化,运行和销毁阶段。

1)初始化阶段
(1)Servlet 容器加载 Servlet 类,把 Servlet 类的 .class 文件中的数据读出内存中。
(2)Servlet 容器创建一个 ServletConfig 对象。 ServletConfig 对象包含了 Servlet 的初始化配置信息。
(3) Servlet 容器创建一个 Servlet 对象。
(4)Servlet 容器调用其 init 方法进行初始化。

2)运行阶段
当 Servlet 接收到一个请求时,Servlet 容器会针对这个请求创建 servletRequest 和 servletResponse 对象,然后调用 service 方法。service 方法通过 servletRequest 对象获取请求信息。在通过 servletResponse 对象生成这个请求的响应结果。然后销毁 servletRequest 和 servletResponse 对象。不管是 post 还是 get 请求,都会由这个 service 方法进行处理。

3)销毁阶段
(1)当 Web 应用被终止时,servlet 容器会先调用 servlet 对象的 destory 方法,然后销毁 servlet 对象,同时也会销毁与 servlet 对象相关联的 servletConfig 对象。可以在 destory 方法中释放 servlet 所占用的资源,如关闭数据库连接,关闭文件输入输出等。
(2)servlet 的框架由两个 Java 包组成:javax.servlet 和 javax.servlet.http。在 javax.servlet 中定义了所以的 servlet 类都必须实现或扩展的通用接口和类,在 javax.servlet.http 中定义了采用 HTTP 通信协议的 HttpServlet 类。
(3)servlet 被设计成请求驱动,servlet 可能包含多个数据项,Web 容器接收到某个 servlet 请求时,servlet 将请求封装成一个 HttpServletRequest 对象,然后把对象传给 servlet 对应的服务方法。
(4)Http 请求包括 delete,get,options,post,put,trace,在HttpServlet 类中分别对应相应的服务方法。doDelete(),doGet()…。

4.1 Servlet 的使用

1)建立 Servlet

public class MyServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("init ...");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet ...");
        ServletContext sc = getServletContext();

        RequestDispatcher rd = sc.getRequestDispatcher("/index.jsp");
        rd.forward(req, resp);
    }
}

2)添加 web.xml 配置

<servlet>
		<servlet-name>myservlet</servlet-name>
		<servlet-class>org.springframework.test.servlet.MyServlet</servlet-class>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>myservlet</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>

4.2 DispatcherServlet 的初始化

在 servlet 初始化阶段会调用 init 方法,DispatcherServlet 的父类 HttpServletBean 重写了该方法。

// HttpServletBean.java
public final void init() throws ServletException {

	// Set bean properties from init parameters.
	// 解析 init-param 并封装值 pvs
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			// 将当前这个 servlet 类转换为一个 BeanWrapper,从而能够以 Spring 的方式来对 init-param 的值进行注入
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			// 注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会是由 ResourceEditor 进行解析
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			// 空实现,留给子类实现
			initBeanWrapper(bw);
			// 属性自动注入
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}

	// Let subclasses do whatever initialization they like.
	// 留给子类扩展
	initServletBean();
}

DispatcherServlet 的初始化过程主要是通过当前的 servlet 类型实例转换为 BeanWrapper 类型实例,以便使用 Spring 中提供的注入功能进行对应属性的注入。这些属性如 contextAttribute,contextClass,nameSpace,contextConfigLocation等,都可以在 web.xml 中以初始化参数的方式配置在 servlet 的声明中,Spring 会将这些参数注入到对应的值中。

1)封装及验证初始化参数
ServletConfigPropertyValues 除了封装属性外还有验证功能

// HttpServletBean.ServletConfigPropertyValues
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
	throws ServletException {

	Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
			new HashSet<>(requiredProperties) : null);

	// init-param
	Enumeration<String> paramNames = config.getInitParameterNames();
	while (paramNames.hasMoreElements()) {
		String property = paramNames.nextElement();
		Object value = config.getInitParameter(property);
		addPropertyValue(new PropertyValue(property, value));
		if (missingProps != null) {
			missingProps.remove(property);
		}
	}

	// Fail if we are still missing properties.
	if (!CollectionUtils.isEmpty(missingProps)) {
		throw new ServletException(
				"Initialization from ServletConfig for servlet '" + config.getServletName() +
				"' failed; the following required properties were missing: " +
				StringUtils.collectionToDelimitedString(missingProps, ", "));
	}
}

上述代码可知,封装属性主要是对初始化的参数进行封装,也就是 servlet 中配置的<init-param>中配置的封装。可以通过对requiredProperties参数的初始化来强制验证某些属性的必要性,这样,在属性封装中,一旦检测到requiredProperties中的属性没有指定初始值,就会抛出异常。

2)将当前 servlet 实例转换为 BeanWrapper 实例
PropertyAccessorFactory.forBeanPropertyAccess 是 Spring 中提供的工具方法,主要是将指定实例转换为 Spring 中可以处理的 BeanWrapper 类性的实例。

3)注册相对于 Resource 的属性编辑器
一旦遇到 Resource 类型的属性将会是由 ResourceEditor 进行解析。

4)属性注入
BeanWrapper 支持属性自动注入,属性如 contextAttribute,contextClass,nameSpace,contextConfigLocation等。

5)servletBean 的初始化
在 ContextLoaderListener 加载的时候以及创建了 WebApplicationContext 实例,而在这个函数中最重要的是对这个实例进一步的补充初始化。查看 initServletBean,父类 FramewworkServlet 实现了 HttpServletBean 中的 initServletBean 方法。

protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
	if (logger.isInfoEnabled()) {
		logger.info("Initializing Servlet '" + getServletName() + "'");
	}
	long startTime = System.currentTimeMillis();

	try {
		// WebApplicationContext 的初始化
		this.webApplicationContext = initWebApplicationContext();
		// 给子类
		initFrameworkServlet();
	}
	catch (ServletException | RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (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";
		logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
				"': request parameters and headers will be " + value);
	}

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

上述方法主要设计了计数器统计初始化时间,并提供了一个扩展方法 initFrameworkServlet 用于子类的扩展,关键的初始化操作委托给了 initWebApplicationContext 方法。

4.3 WebApplicationContext 的初始化

initWebApplicationContext方法的主要工作就是创建或刷新WebApplicationContext实例并对 servlet 功能所使用的变量进行初始化。

protected WebApplicationContext initWebApplicationContext() {
	// parent 为 spring 容器,外层的 webApplicationContext 为 springMvc 容器
	// 获取 servletContext 环境中的 webApplicationContext - spring 容器
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;

	// 在 springboot 中就是通过构造注入的
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		// context 在构造函数中被注入
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				// 刷新上下文环境
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 一般不会设置contextAttribute属性,所以这里返回值为null
	if (wac == null) {
		// No context instance was injected at construction time -> see if one
		// has been registered in the servlet context. If one exists, it is assumed
		// that the parent context (if any) has already been set and that the
		// user has performed any initialization such as setting the context id
		// 通过 contextAttribute 属性加载 webApplicationContext
		wac = findWebApplicationContext();
	}
	// 常规的在 web.xml 文件中配置 ContextLoaderListener 会到这一步
	// 以及经过 configureAndRefreshWebApplicationContext 这一步
	// 构建 springMVC 容器
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		// 新建一个实例
		wac = createWebApplicationContext(rootContext);
	}

	if (!this.refreshEventReceived) {
		// Either the context is not a ConfigurableApplicationContext with refresh
		// support or the context injected at construction time had already been
		// refreshed -> trigger initial onRefresh manually here.
		synchronized (this.onRefreshMonitor) {
			// 刷新
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		// 将 springMVC 容器环境存入 servletContext 中
		//  FrameworkServlet.class.getName() + ".CONTEXT."
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}

上述方法主要逻辑:
1)寻找或创建对应的 WebApplicationContext 实例
WebApplicationContext 的寻找及创建包括以下几个步骤。

(1)通过构造函数的注入进行初始化
this.webApplicationContext != null时,就可以知道其是提供构造函数初始化的。因为在 Web 中包含 SpringWeb 的核心逻辑的 DispatcherServlet 只可以被声明一次,所以,this.webApplicationContext != null 时,就可以确定它是提供构造函数注入的。在 springboot 中就是通过构造注入的。

(2)通过 contextAttribute 进行初始化
通过在 web.xml 文件中配置的 servlet 参数 contextAttribute 来查找 ServletContext 中对应的属性,即在 ContextLoaderListener 对 WebApplicationContext 进行初始化,并默认以WebApplicationContext.class.getName + ".ROOT"作为 key,放入 ServletContext 中。

protected WebApplicationContext findWebApplicationContext() {
	// contextAttribute 属性
	String attrName = getContextAttribute();
	if (attrName == null) {
		return null;
	}
	// 查找 webApplicationContext
	WebApplicationContext wac =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
	if (wac == null) {
		throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
	}
	return wac;
}

(3)重新创建 WebApplicationContext实例
如果以上两种方式都没有找到,只能重新创建新的实例。

// FrameworkServlet.java
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
	return createWebApplicationContext((ApplicationContext) parent);
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
	Class<?> contextClass = getContextClass();
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException(
				"Fatal initialization error in servlet with name '" + getServletName() +
						"': custom WebApplicationContext class [" + contextClass.getName() +
						"] is not of type ConfigurableWebApplicationContext");
	}
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

	wac.setEnvironment(getEnvironment());
	// parent 为在 contextLoaderListener 中创建的实例
	wac.setParent(parent);
	// 获取 contextConfigLocation 属性,配置在 servlet 参数中
	String configLocation = getContextConfigLocation();
	if (configLocation != null) {
		wac.setConfigLocation(configLocation);
	}
	// 初始化 spring 环境包括加载配置文件
	configureAndRefreshWebApplicationContext(wac);

	return wac;
}

2)configureAndRefreshWebApplicationContext
无论是构造函数注入,还是单独创建,都需要调用 configureAndRefreshWebApplicationContext 方法来对以及创建的 WebApplicationContext 实例进行配置及刷新。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// The application context id is still set to its original default value
		// -> assign a more useful id based on available information
		if (this.contextId != null) {
			wac.setId(this.contextId);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
		}
	}

	wac.setServletContext(getServletContext());
	wac.setServletConfig(getServletConfig());
	wac.setNamespace(getNamespace());
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}

	postProcessWebApplicationContext(wac);
	applyInitializers(wac);
	// 加载配置文件及整合 parent 到wac,即 AbstractApplicationContext#refresh
	wac.refresh();
}

无论调用方式如何,只要是使用 ApplicationContext 所提供的功能,都需要调用公共父类 AbstractApplicationContext 的 refresh 方法进行配置文件加载。

3)刷新
onRefresh 是 FrameworkServlet 提供的模板方法,在其子类 DispatcherServlet 中进行了重写,主要用于属性 Spring 在 Web 功能实现中所必须使用的全局变量。如果没有配置对应 bean,会在 DispatcherServlet 所在目录下的 DispatcherServlet.properties 中所定义的内容来加载默认的。

// DispatcherServlet.properties
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

上述是 DispatcherServlet.properties 中的内容,定义了默认的配置。

// DispatcherServlet.java
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
	// 初始化 MultipartResolver
	initMultipartResolver(context);
	// 初始化 LocaleResolver
	initLocaleResolver(context);
	// 初始化 ThemeResolver
	initThemeResolver(context);
	// 初始化 HandlerMappings
	initHandlerMappings(context);
	// 初始化 HandlerAdapters
	initHandlerAdapters(context);
	// 初始化 HandlerExceptionResolvers
	initHandlerExceptionResolvers(context);
	// 初始化 RequestToViewNameTranslator
	initRequestToViewNameTranslator(context);
	// 初始化 ViewResolvers
	initViewResolvers(context);
	// 初始化 FlashMapManager
	initFlashMapManager(context);
}

1)初始化 MultipartResolver
在 Spring 中 MultipartResolver 主要用来处理文件上传。默认情况下,Spring 是没有 multipart 处理的。如果想要使用 Spring 的 multipart,则需要在 Web 应用的上下文中添加 multipart 解析器。这样,每个请求就会被检查是否包含 multipart。如果请求中包含 multipart,那么上下文定义的 MultipartResolver 就会解析它,这样请求中的 multipart 属性就会像其它属性一样被处理。常用解析器配置如下:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<!-- 最大文件上传数量-->
	<property name="maxUploadSize" value="100000" />
</bean>

那么 MultipartResolver 就是在 initMultipartResolver 方法中被加入到 DispatcherServlet 中的。

private void initMultipartResolver(ApplicationContext context) {
	try {
		// 从 spring 容器中取得配置的解析器,并放到 DispatchServlet 中 - multipartResolver
		// 如果 springMVC 容器中没有,回到子容器 spring 容器中寻找
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Detected " + this.multipartResolver);
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// Default is no multipart resolver.
		this.multipartResolver = null;
		if (logger.isTraceEnabled()) {
			logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
		}
	}
}

可以在 spring 配置文件或 springMVC 配置文件中配置。

<!-- spring 容器配置文件-->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!-- springMVC 容器配置文件-->
<!-- 默认为 {servlet-name}-servlet.xml-->
<servlet>
    <servlet-name>springmvc</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:xxx.xml</param-value>
	</init-param>
</servlet>

2)初始化 LocaleResolver
Spring 中国际化配置有 3 种使用方式。

(1)基于 URL 参数的配置
通过 URL 参数来控制国际化,比如在 a 标签中加上 href="?locale=zh_CN。而提供这个功能的就是 AcceptHeaderLocaleResolver。具体配置。

<!-- URL 参数控制国际化配置-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" />

(2)基于 session 的配置
它通过检验用户会话中预置的属性来解析区域。最常用的是根据用户本次会话过程中的语言来决定语言种类(用户登陆时选择语言,则此次登录周期内统一使用此语言设定),如果此会话属性不存在,会根据 accrpt-language HTTP 头部确定默认区域。

<!-- 基于 session 国际化配置, 检验用户会话中预置的属性来解析区域-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />

(3)基于 cookie 的国际化配置
CookieLocaleResolver 通过浏览器的 cookie 设置取得 Locale 对象,这种策略应用在不支持会话或状态必须保存在客户端时有用。

<!-- 基于 cookie 国际化配置,通过浏览器的 cookie 设置取得 Locale 对象,这种策略应用在不支持会话或状态必须保存在客户端时有用-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />

对于 LocaleResolver 的初始化是在 DispatcherServlet 中。

private void initLocaleResolver(ApplicationContext context) {
	try {
		// 从 spring 容器中取得配置的解析器,并放到 DispatchServlet 中 - localeResolver
		this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Detected " + this.localeResolver);
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// We need to use the default.
		this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
					"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
		}
	}
}

SpringMVC 中的默认配置

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

3)初始化 ThemeResolver
在 Web 开发中会遇到通过主题 Theme 来控制网页风格。一个主题就是一组静态资源(样式和图片)。Spring 主题功能的构成主要包括。

(1)主题资源
org.springframework.ui.context.ThemeSource是 Spring 主题资源的接口,Spring 的主题资源需要通过 ThemeSource 接口来实现存放主题信息的资源。默认实现类是ResourceBundleThemeSource

<!-- 主题资源-->
<bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource">
	<property name="basenamePrefix" value="xxx.xx."/>
</bean>

默认状态下是在类路径根目录下查找相应的资源文件,也可以通过 basenamePrefix 来定制。

(2)主题解析器
org.springframework.web.servlet.ThemeResolver 是主题解析器的接口,主要有 3 个实现。以主题文件按 summer.properties 为例。

  1. FixedThemeResolver 用于选择一个固定的主题。
<!-- 固定的解析器-->
<bean id="themeResolver" class="org.springframework.web.servlet.theme.SessionThemeResolver">
	<property name="defaultThemeName" value="summer" />
</bean>
  1. CookieThemeResolver 用于实现用户所选的主题,以 Cookie 的形式存放在客户的机器上。
<!-- 用于实现用户所选的主题,以 Cookie 的形式存放在客户的机器上-->
<bean id="themeResolver" class="org.springframework.web.servlet.theme.FixedThemeResolver">
	<property name="defaultThemeName" value="summer" />
</bean>
  1. SessionThemeResolver 用于主题保存在用户的 HttpSession 中。
<!-- 用于主题保存在用户的 HttpSession 中-->
<bean id="themeResolver" class="org.springframework.web.servlet.theme.CookieThemeResolver">
	<property name="defaultThemeName" value="summer" />
</bean>
  1. AbstractThemeResolver 抽象类,被 SessionThemeResolver 和 FixedThemeResolver 所继承。用户可以继承他来自定义主题解析器。

(3)拦截器
如果需要根据用户请求来改变主题,那么 Spring 提供了一个以及实现的拦截器 ThemeChangeInterceptor。

!-- 主题改变拦截器,接收 url 请求,加上参数 ?themeName=具体的主题名称-->
<bean id="themeChangeInterceptor" class="org.springframework.web.servlet.theme.ThemeChangeInterceptor">
	<property name="paramName" value="themeName" />
</bean>

接收 url 请求,加上参数 ?themeName=具体的主题名称。还需要在 HandlerMappings 中添加拦截器。

<property name="interceptors">
    <list>
        <ref local="themeChangeInterceptor" />
    </list>
</property>

Theme 解析器的初始化和其它变量初始化一样。

private void initThemeResolver(ApplicationContext context) {
	try {
		// 从 spring 容器中取得配置的解析器,并放到 DispatchServlet 中 - themeResolver
		this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Detected " + this.themeResolver);
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// We need to use the default.
		this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +
					"': using default [" + this.themeResolver.getClass().getSimpleName() + "]");
		}
	}
}

SpringMVC 默认文件中配置。

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

4)初始化 HandlerMapings
当客户端发出 Request 时 Dispathcer 会将 Request 提交给 HandlerMappings,然后 HandlerMappings 根据 Web Application Context 的配置来回传给 DispatcherServlet 相应 Controller。

在基于 SpringMVC 的基础上可以为 DispatcherServlet 提供多个 HandlerMapping。在使用中,会将指定的一系列 HandlerMapping 的优先级进行排序,优先使用优先级在前的的 HandlerMapping。如果当前的 HandlerMapping 能够返回可以的 Handler,DispatcherServlet 会使用当前返回的 Handler 进行 请求的处理,否则会继续询问下一个 HandlerMapping(参照责任链模式)。初始化配置。

private void initHandlerMappings(ApplicationContext context) {
	// 初始设置为空
	this.handlerMappings = null;

	// 是否查找所有的 handlerMapping
	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		// 获取所有的 HandlerMapping
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			// 初始化赋值
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			// 只配置了一个 detectAllHandlerMappings 配置了 false 的时候 - handlerMapping
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}

	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		// 在 DispatcherServlet 所在目录下的 DispatcherServlet.properties 中所定义的内容来加载默认的 HandlerMapping,
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

默认情况下 SpringMVC 会加载系统中所有实现了 HandlerMapping 接口的 bean。如果修改了 web.xml 中 DispatcherServlet 中的初始化参数。

<init-param>
	<param-name>detectAllHandlerMappings</param-name>
	<param-value>false</param-value>
</init-param>

那么只会加载名称为 handlerMapping 的 bean。如果都没有配置,那么 SpringMVC 会在 DispatcherServlet 所在目录下的 DispatcherServlet.properties 中所定义的内容来加载默认的。

SpringMVC 默认配置。

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

5)初始化 HandlerAdapters
适配器模式的应用,将一个类的接口适配成用户所期待的。使用适配器可以使接口不兼容而无法在一起工作的类中协调工作。即将类自己的接口包裹在一个已存在的类中。处理 handler 时使用到的适配器模式。初始化逻辑。

private void initHandlerAdapters(ApplicationContext context) {
	this.handlerAdapters = null;

	// 是否查找所有的 handlerAdapters
	if (this.detectAllHandlerAdapters) {
		// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
		// 获取所有的 HandlerAdapter
		Map<String, HandlerAdapter> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerAdapters = new ArrayList<>(matchingBeans.values());
			// We keep HandlerAdapters in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerAdapters);
		}
	}
	else {
		try {
			// 只配置了一个 detectAllHandlerAdapters 配置了 false 的时候 - handlerAdapter
			HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
			this.handlerAdapters = Collections.singletonList(ha);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerAdapter later.
		}
	}

	// Ensure we have at least some HandlerAdapters, by registering
	// default HandlerAdapters if no other adapters are found.
	if (this.handlerAdapters == null) {
		// 在 DispatcherServlet 所在目录下的 DispatcherServlet.properties 中所定义的内容来加载默认的 HandlerAdapter
		this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

初始化方式和 HandlerMappings 的类似。如果无法找到对应的 bean,系统会尝试加载默认的适配器。

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
	// 获取 class 的 类路径名称
	String key = strategyInterface.getName();
	// 从配置中获取,开始会在静态代码中加载配置文件。
	String value = defaultStrategies.getProperty(key);
	if (value != null) {
		// 获取所有的实现类路径,以 英文逗号 分割
		String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
		List<T> strategies = new ArrayList<>(classNames.length);
		// 反射实例化所有 bean
		for (String className : classNames) {
			try {
				Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
				Object strategy = createDefaultStrategy(context, clazz);
				strategies.add((T) strategy);
			}
			catch (ClassNotFoundException ex) {
				throw new BeanInitializationException(
						"Could not find DispatcherServlet's default strategy class [" + className +
						"] for interface [" + key + "]", ex);
			}
			catch (LinkageError err) {
				throw new BeanInitializationException(
						"Unresolvable class definition for DispatcherServlet's default strategy class [" +
						className + "] for interface [" + key + "]", err);
			}
		}
		return strategies;
	}
	else {
		return new LinkedList<>();
	}
}

在该方法中,会从 defaultStrategies 中加载对应的属性。defaultStrategies 在静态代码中初始化。

static {
	// Load default strategy implementations from properties file.
	// This is currently strictly internal and not meant to be customized
	// by application developers.
	try {
		ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
		throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
	}
}

SpringMVC 默认只加载。

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

以上三个适配器。作为总控制器的派遣器 servlet 提供处理器映射器得到处理器后,会轮询处理器适配器模块,查找能够处理当前 HTTP 请求的处理器适配器的实现,处理器模块根据处理器映射返回的处理器类型,例如简单的控制器类型,注解控制器类型或远程调用处理器类型,来选择某个适当的处理器适配器的实现,从而适配当前的 HTTP 请求。

(1)HTTP 请求处理器适配器(HttpRequestHandlerAdapter)
HTTP 请求处理器适配器仅仅支持对 HTTP 请求处理器的适配。它简单的将 HTTP 请求对象何响应对象传递给 HTTP 请求处理器的实现,它并不需要返回值。主要应用在基于 HTTP 的远程调用的实现上。底层是调用 HttpRequestHandler.handlerRequest(request,response)。HttpRequestHandler 接口的实现。

(2)简单控制器处理器适配器(SimpleControllerHandlerAdapter)
这个适配器将 HTTP 请求适配到一个控制器的实现进行处理,这里控制器的实现是一个简单的控制器接口的实现。简单控制器处理器适配器被设计成一个框架类的实现,不需要被改写,客户化的业务通常是在控制器接口的实现类中实现的。底层调用Controller.handlerRequest(request,response)。控制器指的是 Conoller 接口的实现。

(3)请求映射处理适配器(RequestMappingHandlerAdapter)
主要用于将某个请求适配给 @RequestMapping 类型的 Handler 处理。

6)初始化 initHandlerExceptionResolvers
基于 HandlerExceptionResolver 接口的异常处理,使用这种方式只需要实现 resolveException 方法,该方法返回一个 ModelAndView 对象,在在方法内部对异常的类型进行判判断然后尝试生成对应的 ModelAndView 对象,如果该方法返回了 null,则 Spring 会继续寻找其他的实现了 HandlerExceptionResolver 接口的 bean。即 Spring 会搜索所有注册在其环境中的实现了 HandlerExceptionResolver 接口的 bean,逐个执行,直到返回一个 ModelAndView 对象。初始化代码:

private void initHandlerExceptionResolvers(ApplicationContext context) {
	this.handlerExceptionResolvers = null;

	// 获取所有的 handlerExceptionResolver
	if (this.detectAllHandlerExceptionResolvers) {
		// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
				.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
			// We keep HandlerExceptionResolvers in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
		}
	}
	else {
		try {
			// 只配置了一个 detectAllHandlerAdapters 配置了 false 的时候 - handlerExceptionResolver
			HandlerExceptionResolver her =
					context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
			this.handlerExceptionResolvers = Collections.singletonList(her);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, no HandlerExceptionResolver is fine too.
		}
	}

	// Ensure we have at least some HandlerExceptionResolvers, by registering
	// default HandlerExceptionResolvers if no other resolvers are found.
	if (this.handlerExceptionResolvers == null) {
		//  在 DispatcherServlet 所在目录下的 DispatcherServlet.properties 中所定义的内容来加载默认的 HandlerExceptionResolver
		this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

默认配置:ExceptionHandlerExceptionResolver(全局异常处理),ResponseStatusExceptionResolver(响应状态异常),DefaultHandlerExceptionResolver(默认异常解析)。

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

7)初始化 RequestToViewNameTranslator
当 Controller 处理器方法没有返回一个 View 对象或逻辑视图名称,并且在该方法中没有直接往 response 的输出流里写数据时,Spring 就会采用约定好的方式提供一个逻辑视图名称。该视图名称从 RequestToViewNameTranslator 的 getViewName 方法中取得,我们可以实现 RequestToViewNameTranslator 接口来约定好在没有返回视图名称的情况下如何确定视图名称。 DefaultRequestToViewNameTranslator 是 Spring 的默认实现。主要属性定义:

  1. prefix:前缀,表示约定好的视图名称需要加上的前缀,默认空串。
  2. suffix:后缀,表示约定好的视图名称需要加上的后缀,默认空串。
  3. separator:分隔符,默认 “/”。
  4. stripLeadingSlash:如果首字符是分隔符,是否需要去除,默认 true。
  5. stripTrailingSlash:如果尾字符是分隔符,是否需要去除,默认 true。
  6. urlDecode:是否需要对 URL 解码,默认 true。会采用 request 指定的编码或 ISO-8859-1 对 URL 进行解码。

Spring 中默认的为 DefaultRequestToViewNameTranslator。DefaultRequestToViewNameTranslator 会获取到请求的 URI,然后根据提供的属性进行一些改造,把改造后的结果作为视图名称返回。以 http://localhost/app/test/index.html 为例。其对应的请求 URI 为 test/index.html。以下分不同情况返回的视图名称。

  1. prefix 和 suffix 都存在,其他值是默认值,则逻辑视图名称为 {prefix}test/index{suffix}。
  2. stripLeadingSlash 和 stripTrailingSlash 都为 false,其他默认,则逻辑视图名称为 /test/index.html。
  3. 都采用默认配置,则逻辑视图名称为 test/index。

如果逻辑视图名称和请求路径相同或相关关系都是一样的,就可以使用 DefaultRequestToViewNameTranslator。初始化逻辑:

private void initRequestToViewNameTranslator(ApplicationContext context) {
	try {
		// 查找用户是否自定义配置 - viewNameTranslator
		this.viewNameTranslator =
				context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Detected " + this.viewNameTranslator);
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// We need to use the default.
		// 使用默认的配置 - DefaultRequestToViewNameTranslator
		this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
					"': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");
		}
	}
}

SpringMVC 默认配置:

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

8)初始化 ViewResolvers
在 SpringMvc 中,当 Controller 将请求处理结果放入到 ModelAndView 中后,DispatcherServlet 会根据 ModelAndView 选择合适的视图进行渲染。在 ViewResolver 中进行选择和渲染。在 ViewResolver 中定义了 resolveViewName 方法,根据 viewName 场景合适类型的 view。

可以在 Spring 中进行配置。

<!-- 该 bean 会在 ModelAndView 返回的视图名前加上 prefix前缀,在加上 suffix 后缀-->
<bean  id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/WBE-INF/jsp/" />
	<property name="suffix" value=".jsp" />
</bean>

默认为 InternalResourceViewResolver。初始化在 initViewResolvers 方法中完成。

private void initViewResolvers(ApplicationContext context) {
	this.viewResolvers = null;

	// 寻找所有的 ViewResolver
	if (this.detectAllViewResolvers) {
		// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
		Map<String, ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.viewResolvers = new ArrayList<>(matchingBeans.values());
			// We keep ViewResolvers in sorted order.
			AnnotationAwareOrderComparator.sort(this.viewResolvers);
		}
	}
	else {
		try {
			// 只配置了一个 detectAllViewResolvers 配置了 false 的时候 - viewResolver
			ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
			this.viewResolvers = Collections.singletonList(vr);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default ViewResolver later.
		}
	}

	// Ensure we have at least one ViewResolver, by registering
	// a default ViewResolver if no other resolvers are found.
	if (this.viewResolvers == null) {
		//  在 DispatcherServlet 所在目录下的 DispatcherServlet.properties 中所定义的内容来加载默认的 InternalResourceViewResolver
		this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

SpringMVC 默认配置。

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

9)初始化 FlashMapManager
SpringMVC Flash attributes 提供了一个请求存储属性,可供其他请求使用。在使用重定向的时候十分必要,例如 Post/Redirect/Get 模式。Flash attributes 在重定向之前暂存(就像存在 session 中)以便重定向之后还能使用,并立即删除。

SpringMVC 提供两个主要的抽象来支持 flash attributes。FlasMap 用于保持 flash attributes,而 FlasMapManager 用于存储,检索,管理 FlashMap 实例。

flash attributes 支持默认开启(“on”) 并不需要显示开启,它不会导致 HTTP Session 的创建。这两个 FalshMap 实例都可以通过静态方法 RequestContextUtils 从 SpringMVC 的任何位置访问。初始化在 initFlashMapManager 方法中实现。默认是 SessionFlashMapManager。

private void initFlashMapManager(ApplicationContext context) {
	try {
		// 加载 flashMapManager
		this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Detected " + this.flashMapManager);
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// We need to use the default.
		// 从配置文件中加载 - SessionFlashMapManager
		this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +
					"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");
		}
	}
}

SpringMVC 默认配置。

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

5 DispatcherServlet 的逻辑处理

在 HttpServlet 类中分别 提供了相应的服务方法,doGet,doDelete,doOptions,doPost,doPut和doTrace,它会根据不同的形式将程序引导至对应的函数进行处理。

由于 DispatcherServlet 继承了 HttpServlet 类,依次会首先调用其service方法。

// FrameworkServlet.java
protected void service(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {

	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
		processRequest(request, response);
	}
	else {
		super.service(request, response);
	}
}

// HttpServlet.java
protected void service(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException
{
    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < lastModified) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
        
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
        
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
        
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
        
    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

其中用的最多的就是doGetdoPost方法。

// FrameworkServlet.java
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {

	// 委托给 processRequest 方法进行处理
	processRequest(request, response);
}

protected final void doPost(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {

	processRequest(request, response);
}

实际逻辑委托给了processRequest方法。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {

	// 记录请求处理时间
	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;

	// 国际化上下文
	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	LocaleContext localeContext = buildLocaleContext(request);

	// 请求属性
	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
	ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

	// 绑定到当前线程 - Local
	initContextHolders(request, localeContext, requestAttributes);

	try {
		// 给子类实现
		doService(request, response);
	}
	catch (ServletException | IOException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (Throwable ex) {
		failureCause = ex;
		throw new NestedServletException("Request processing failed", ex);
	}

	finally {
		// 恢复线程到原始状态
		resetContextHolders(request, previousLocaleContext, previousAttributes);
		if (requestAttributes != null) {
			requestAttributes.requestCompleted();
		}
		logResult(request, response, failureCause, asyncManager);
		// 发布事件通知
		publishRequestHandledEvent(request, response, startTime, failureCause);
	}
}

上述方法主要逻辑:

  1. 为了保证当前线程的的 LocaleContext 和 RequestAttributes 可以在当前请求后还能恢复。提取当前线程的两个属性。
  2. 根据当前 request 创建对应 LocaleContext 和 RequestAttributes,并绑定到当前线程。
  3. 委托给 doService 方法进一步处理。
  4. 请求处理结束后恢复线程到原始状态。
  5. 请求结束后无论成功与否发布事件通知。

继续查看 doService 方法。

// DispatcherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	logRequest(request);

	// Keep a snapshot of the request attributes in case of an include,
	// to be able to restore the original attributes after the include.
	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(DEFAULT_STRATEGIES_PREFIX)) {
				attributesSnapshot.put(attrName, request.getAttribute(attrName));
			}
		}
	}

	// Make framework objects available to handlers and view objects.
	// 将一些辅助工具变量设置到 request 中
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, 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);
	}

	try {
		// 分发请求
		doDispatch(request, response);
	}
	finally {
		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Restore the original attribute snapshot, in case of an include.
			if (attributesSnapshot != null) {
				restoreAttributesAfterInclude(request, attributesSnapshot);
			}
		}
	}
}

上述方法同样是作一些准备工作,即将 localResolver、themeResolver 等设置在 request 属性中,因为这个属性后面的处理总会用到。

继续查看doDispatch方法。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	//是否是multipart/form-data 请求
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

		try {
			// 如果是 MultipartContent 类型,则转换成了 MultipartHttpServletRequest
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);

			// Determine handler for the current request.
			// 根据请求获取处理链
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) {
				// 没找到对应 Handler 的处理,默认情况下可以设置默认的 Handler。
				noHandlerFound(processedRequest, response);
				return;
			}

			// Determine handler adapter for the current request.
			// 获取处理器适配器,根据处理器获取对应的适配器
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// Process last-modified header, if supported by the handler.
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			//
			if (isGet || "HEAD".equals(method)) {
				// 获取最近一次修改时间
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				// 检查是否没有修改
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			//执行所有拦截器的preHandle方法,是否可执行
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			// Actually invoke the handler.
			// 将请求转换为需要的接口,
			//HttpRequestHandlerAdapter 对应 HttpRequestHandler
			//SimpleServletHandlerAdapter 对应Servlet
			//SimpleControllerHandlerAdapter 对象 Controller
			//AbstractHandlerMethodAdapter需要子类实现handle
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}

			applyDefaultViewName(processedRequest, mv);
			// 执行所有拦截器中的postHandle方法
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		catch (Throwable err) {
			// As of 4.3, we're processing Errors thrown from handler methods as well,
			// making them available for @ExceptionHandler methods and other scenarios.
			dispatchException = new NestedServletException("Handler dispatch failed", err);
		}
		// 处理结果,对 mv 进行处理,异常或者正常逻辑,并跳转客户端页面
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Throwable err) {
		triggerAfterCompletion(processedRequest, response, mappedHandler,
				new NestedServletException("Handler processing failed", err));
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			// Instead of postHandle and afterCompletion
			if (mappedHandler != null) {
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}

上述方法展示了 Spring 请求处理所涉及的主要逻辑。主要逻辑如下。

5.1 MultipartContent 类型的 request 处理

对于请求的处理,Spring 首先考虑的是对于 Multipart 的处理,如果是 MultipartContent 类中的 request,则转换 request 为 MultipartHttpServletRequest 类型的 request。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	// 如果设置了 multipartResolver 且 request 是 MultipartContent 类型的
	if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
		if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
			if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
				logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
			}
		}
		else if (hasMultipartException(request)) {
			logger.debug("Multipart resolution previously failed for current request - " +
					"skipping re-resolution for undisturbed error rendering");
		}
		else {
			try {
				// 解析成 MultipartHttpServletRequest
				return this.multipartResolver.resolveMultipart(request);
			}
			catch (MultipartException ex) {
				if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
					logger.debug("Multipart resolution failed for error dispatch", ex);
					// Keep processing error dispatch with regular request handle below
				}
				else {
					throw ex;
				}
			}
		}
	}
	// If not returned before: return original request.
	return request;
}

5.2 根据 request 信息寻找对应的 Handler

在 Spring 中最简单的映射处理器配置如下。

<bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
	<property name="mappings">
		<props>
			<prop key="/userlist.jsp">userController</prop>
		</props>
	</property>
</bean>

在 Spring 加载过程中,Spring 会将类型为 SimpleUrlHandlerMapping 的实例加载到 this.handlerMappings 中,所有根据 request 获取对应的 Handler,就是获取 userController,但是 userController 继承自 AbstractController 类型,与 HandlerExecutionChain 无关联,那么这一步是如何封装的?

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 如果映射器不为空
	if (this.handlerMappings != null) {
		for (HandlerMapping mapping : this.handlerMappings) {
			// 根据映射器找到相应的执行链
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

在系统启动时,Spring 会将所以的映射类型的 bean 注册到this.handlerMappings变量中,因此次方法就是遍历所有的HandlerMapping,并调用其getHandler方法进行处理。以 SimpleUrlHandlerMapping 为例查看其 getHandler 方法。

// AbstractHandlerMapping.java
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 根据 request 获取对应的 handler
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		// 如果没有对应 request 的 handler 则使用默认的 handler
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}

	// 将 handler 封装成 HandlerExecutionChain
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	if (logger.isTraceEnabled()) {
		logger.trace("Mapped to " + handler);
	}
	else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
		logger.debug("Mapped to " + executionChain.getHandler());
	}

	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}

	return executionChain;
}

上述方法中首先使用 getHandlerInternal 方法根据 request 信息获取对应的 Handler,如果以 SimpleUrlHandlerMapping 为例子,则应该是根据 URL 查找 Controller,如果没有找到,会去查找默认的处理器。如果获取的 Controller 为 String 时,则表示配置的是 beanName,需要根据 bean 名称查找对应的 bean。最后通过 getHandlerExecutionChain 对返回的 Handler 进行封装,以保证满足返回类型的匹配。下面进行分析此过程。

1)根据 request 查找对应的 Handler

// AbstarctUrlHandlerMapping.java
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
	// 截取用于匹配的 url 有效路径
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	// 根据路径查找 Hadnler
	Object handler = lookupHandler(lookupPath, request);
	// 没有找到则进行其他处理
	if (handler == null) {
		// We need to care for the default handler directly, since we need to
		// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
		Object rawHandler = null;
		if ("/".equals(lookupPath)) {
			// 如果是 “/” 则使用 RootHandler 进行处理
			rawHandler = getRootHandler();
		}
		if (rawHandler == null) {
			// 如果找不到使用默认的 handler
			rawHandler = getDefaultHandler();
		}
		if (rawHandler != null) {
			// Bean name or resolved handler?
			// 根据 beanName 获取对应的 bean
			if (rawHandler instanceof String) {
				String handlerName = (String) rawHandler;
				rawHandler = obtainApplicationContext().getBean(handlerName);
			}
			// 模板方法
			validateHandler(rawHandler, request);
			handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
		}
	}
	return handler;
}

查看 lookupHandler 方法。

// AbstarctUrlHandlerMapping.java
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
	// Direct match?
	// 如果直接匹配
	Object handler = this.handlerMap.get(urlPath);
	if (handler != null) {
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		return buildPathExposingHandler(handler, urlPath, urlPath, null);
	}

	// Pattern match?
	// 调配符的处理
	List<String> matchingPatterns = new ArrayList<>();
	for (String registeredPattern : this.handlerMap.keySet()) {
		if (getPathMatcher().match(registeredPattern, urlPath)) {
			matchingPatterns.add(registeredPattern);
		}
		else if (useTrailingSlashMatch()) {
			if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
				matchingPatterns.add(registeredPattern + "/");
			}
		}
	}
	String bestMatch = null;
	Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
	if (!matchingPatterns.isEmpty()) {
		matchingPatterns.sort(patternComparator);
		if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
			logger.trace("Matching patterns " + matchingPatterns);
		}
		bestMatch = matchingPatterns.get(0);
	}
	if (bestMatch != null) {
		handler = this.handlerMap.get(bestMatch);
		if (handler == null) {
			if (bestMatch.endsWith("/")) {
				handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
			}
			if (handler == null) {
				throw new IllegalStateException(
						"Could not find handler for best pattern match [" + bestMatch + "]");
			}
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);

		// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
		// for all of them
		Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
		for (String matchingPattern : matchingPatterns) {
			if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
				Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
				Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
				uriTemplateVariables.putAll(decodedVars);
			}
		}
		if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
			logger.trace("URI variables " + uriTemplateVariables);
		}
		return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
	}

	// No handler found...
	return null;
}

根据 URL 获取对应的 Handler 的匹配规则主要考虑了直接匹配和通配符匹配。其 buildPathExposingHandler 方法将 Handler 封装成了 HandlerExecutionChain 类型。

// AbstarctUrlHandlerMapping.java
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
	String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {

	// 新建 HandlerExecutionChain 实例
	HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
	// 添加拦截器
	chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
	if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
		chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
	}
	return chain;
}

上述方法中将 Handler 以参数的形式传入,并构建 HandlerExecutionChain 类型实例,加入了两个拦截器。这是使用了链处理机制,是 Spring 中常用的处理方式,是 AOP 中的重要组成部分,可以方便的对目标对象进行扩展以及拦截。

2)加入拦截器执行链
getHandlerExecutionChain 函数最主要的目的是将配置中对应的拦截器加入到执行链中,以保证这些拦截器可以有效作用于目标对象。

// AbstractHandlerMapping.java
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				// 加入拦截器
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

5.3 没有找到对应的 Handler 的异常处理

每个请求都应该对应一个 Handler,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在 Handler 中,所以一旦没有找到 Handler 的情况(正常情况下如果没有 URL 匹配的 Handler,可以设置默认的 Handler 来处理请求,当没有默认的情况下,会出现 Handler 为空的情况),就只能通过 response 向用户返回错误信息。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (pageNotFoundLogger.isWarnEnabled()) {
		pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
	}
	if (this.throwExceptionIfNoHandlerFound) {
		throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
				new ServletServerHttpRequest(request).getHeaders());
	}
	else {
		// 通过 response 写出 404 错误信息
		response.sendError(HttpServletResponse.SC_NOT_FOUND);
	}
}

5.4 根据当前 Handler 寻找对应的 HandlerAdapter

在 WebApplicationContext 的初始化过程中有 HandlerAdapter 的初始化,在默认情况下普通的 Web 请求会交给 SimpleControllerHandlerAdapter 去处理。所以以 SimpleControllerHandlerAdapter 为例分析获取适配器的逻辑。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			// 默认 handlerAdapters 中有 3 中适配器
			// HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter, RequestMappingHandlerAdapter
			// 调用具体的适配器的 supports 方法查看是否支持该 Handler
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

上述方法主要遍历所有的适配器来选择合适的适配器并返回。而某个适配器是否适用于当前 handler 逻辑封装到了具体的适配器中,进一步查看 SimpleControllerHandlerAdapter 的 supports 方法。

// SimpleControllerHandlerAdapter.java
public boolean supports(Object handler) {
	return (handler instanceof Controller);
}

HttpRequestHandlerAdapter 的判断,可以看出是否实现了 HttpRequestHandler 接口。

@Override
public boolean supports(Object handler) {
	return (handler instanceof HttpRequestHandler);
}

RequestMappingHandlerAdapter 的判断,实现 HandlerMethod 接口且 supportsInternal 方法返回 true,在 RequestMappingHandlerAdapter 中 supportsInternal 方法默认返回 true。

// AbstractHandlerMethodAdapter.java
@Override
public final boolean supports(Object handler) {
	return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

// RequestMappingHandlerAdapter.java
protected boolean supportsInternal(HandlerMethod handlerMethod) {
	return true;
}

5.5 缓存处理

在研究 Spring 对缓存处理的功能支持之前,先了解下 Last-Modified 机制。

  1. 在客户端第一次输入 URL 时,服务端会返回内容和状态码 200,同时会添加一个 Last-Modified 的响应头,表示此文件服务器上的最后更新时间 “Last-Modified:Wed, 14 Mar 2012 10:22:42 GMT”,表示最后更新时间 2020-03-14 10:22:42。
  2. 客户端第二次请求该 URL 时,客户端会向服务器发送请求头 “If-Modified-Since”,询问服务器该时间之后当前请求内容是否有过修改,如"If-Modified-Since:Wed, 14 Mar 2012 10:22:42 GMT",如果内如没有变化,则自动返回 HTTP 304 状态码(只有响应头,内容为空),这样节省了带宽。
  3. Spring 提供对 Last-Modified 机制的支持,至需要实现 LastModified 接口。

如下实例。

public class UserController extends AbstractController implements LastModified {

	private long lastModified;

	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
		List<User> list = new ArrayList<>(2);
		User userA = new User(1, "张三", 12, "男");
		User userB = new User(2, "李四", 15, "女");
		list.add(userA);
		list.add(userB);
		return new ModelAndView("userlist", "users", list);
	}

	@Override
	public long getLastModified(HttpServletRequest request) {
		if(lastModified == 0) {
			lastModified = System.currentTimeMillis();
		}

		return lastModified;
	}
}

UserController 只需要实现 LastModified 接口的 getLastModified 方法,保证当内容发生改变时返回最新的修改时间即可。Spring 判断是否过期,提供判断请求的 If-Modified-Since 是否大于当前 getLastModified 方法返回的时间戳,如果是则表示没有修改。 只有 get 方法或 HEAD 进行缓存处理。

String method = request.getMethod();
boolean isGet = "GET".equals(method);
// 缓存
if (isGet || "HEAD".equals(method)) {
	// 获取最近一次修改时间
	long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
	// 检查是否没有修改
	if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
		return;
	}
}

5.6 HandlerInterceptor 的处理

Servlet API 定义的 servlet 过滤器可以在 servlet 处理每一个 Web 请求的前后分别对它进行处理。有些时候,只想处理由某些 SpringMVC 处理程序处理的 Web 请求,并在这些处理程序返回的模型属性被传递到视图之前,对其进行操作。

SpringMVC 运行通过拦截器处理拦截 Web 请求,进行前置和后置处理。拦截器在 Web 应用程序的上下文中配置。处理拦截是针对特殊的处理程序映射进行注册的,因此只拦截通过这些处理程序映射的请求。需要实现 HandlerInterceptor 接口,包含 3 个需要实现的方法 preHandler,postHandler,afterCompletion。前两个方法分别是在处理程序之前和之后调用。最后一个方法是在所以请求处理完成后调用。拦截器的注入在 handlerMapping 初始化时注入。

//执行所有拦截器的preHandle方法,是否可执行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
	return;
}

// HandlerExecutionChain.java
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = 0; i < interceptors.length; i++) {
			HandlerInterceptor interceptor = interceptors[i];
			// 拦截其的前置处理
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}

// 执行所有拦截器中的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

// HandlerExecutionChain.java
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
		throws Exception {

	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = interceptors.length - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = interceptors[i];
			// 拦截其的后置处理
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}
}

5.7 逻辑处理

对于逻辑的处理是通过适配器中转调用 Handler 并返回视图的。

// 将请求转换为需要的接口,
//HttpRequestHandlerAdapter 对应 HttpRequestHandler
//SimpleServletHandlerAdapter 对应Servlet
//SimpleControllerHandlerAdapter 对象 Controller
//AbstractHandlerMethodAdapter需要子类实现handle
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

以 SimpleControllerHandlerAdapter 为例子进行分析。

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
	throws Exception {

	return ((Controller) handler).handleRequest(request, response);
}

通过 handleRequest 方法处理。

// AbstractController.java
@Override
@Nullable
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
		throws Exception {

	if (HttpMethod.OPTIONS.matches(request.getMethod())) {
		response.setHeader("Allow", getAllowHeader());
		return null;
	}

	// Delegate to WebContentGenerator for checking and preparing.
	checkRequest(request);
	prepareResponse(response);

	// Execute handleRequestInternal in synchronized block if required.
	if (this.synchronizeOnSession) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {
				return handleRequestInternal(request, response);
			}
		}
	}
	// 真正调用,子类实现
	return handleRequestInternal(request, response);
}

由其 handleInternal 方法实现。

// AbstractUrlViewController.java
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
	String viewName = getViewNameForRequest(request);
	if (logger.isTraceEnabled()) {
		logger.trace("Returning view name '" + viewName + "'");
	}
	return new ModelAndView(viewName, RequestContextUtils.getInputFlashMap(request));
}

5.8 异常视图的处理

在程序运行中出现异常,需要告诉客户端在处理逻辑的过程中出现了异常。Spring 的异常处理机制会将逻辑引导至 HandlerExceptionResolver 的 resolveException 方法。其在 WebApplicationContext 初始化过程中初始化。

// 处理结果,对 mv 进行处理,异常或者正常逻辑,并跳转客户端页面
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

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) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			// 异常处理
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}

	// Did the handler return a view to render?
	if (mv != null && !mv.wasCleared()) {
		// 跳转页面
		render(mv, request, response);
		if (errorView) {
			WebUtils.clearErrorRequestAttributes(request);
		}
	}
	else {
		if (logger.isTraceEnabled()) {
			logger.trace("No view rendering, null ModelAndView returned.");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	if (mappedHandler != null) {
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

processHandlerException 方法进行异常处理。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
	@Nullable Object handler, Exception ex) throws Exception {

	// Success and error responses may use different content types
	request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

	// Check registered HandlerExceptionResolvers...
	ModelAndView exMv = null;
	if (this.handlerExceptionResolvers != null) {
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
			// 调用解析器进行解析,返回 mv
			exMv = resolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
	}
	if (exMv != null) {
		if (exMv.isEmpty()) {
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		// We might still need view name translation for a plain error model...
		if (!exMv.hasView()) {
			String defaultViewName = getDefaultViewName(request);
			if (defaultViewName != null) {
				exMv.setViewName(defaultViewName);
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Using resolved error view: " + exMv, ex);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Using resolved error view: " + exMv);
		}
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}

	throw ex;
}

5.9 根据视图跳转页面

无论是一个系统还是一个站点,最重要的就是与用户进行交互,用户操作系统后发下的命令无论成功与否都需要给用户一个反馈,以便用户进行下一步的操作。所以在逻辑处理的最后会进行页面的跳转。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
	// 国际化解析
	Locale locale =
			(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
	response.setLocale(locale);

	View view;
	String viewName = mv.getViewName();
	if (viewName != null) {
		// We need to resolve the view name.
		// 根据视图名解析获取视图
		view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
		if (view == null) {
			throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
					"' in servlet with name '" + getServletName() + "'");
		}
	}
	else {
		// No need to lookup: the ModelAndView object contains the actual View object.
		view = mv.getView();
		if (view == null) {
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
					"View object in servlet with name '" + getServletName() + "'");
		}
	}

	// Delegate to the View object for rendering.
	if (logger.isTraceEnabled()) {
		logger.trace("Rendering view [" + view + "] ");
	}
	try {
		if (mv.getStatus() != null) {
			response.setStatus(mv.getStatus().value());
		}
		// 页面的跳转
		view.render(mv.getModelInternal(), request, response);
	}
	catch (Exception ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Error rendering view [" + view + "]", ex);
		}
		throw ex;
	}
}

1)解析视图名称
DispatcherServlet 会根据 ModelAndView 选择合适的视图来进行渲染,这功能是在 resolveViewName 方法中实现的。

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
	Locale locale, HttpServletRequest request) throws Exception {

	if (this.viewResolvers != null) {
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
	}
	return null;
}

以 InternalResourceViewResolver 为例进行分析。resolveViewName 方法在父类 AbstractCachingViewResolver 中实现。

// AbstractCachingViewResolver.java
public View resolveViewName(String viewName, Locale locale) throws Exception {
	// 没有缓存,创建新的视图
	if (!isCache()) {
		return createView(viewName, locale);
	}
	else {
		// 直接从缓存中获取
		Object cacheKey = getCacheKey(viewName, locale);
		View view = this.viewAccessCache.get(cacheKey);
		if (view == null) {
			synchronized (this.viewCreationCache) {
				view = this.viewCreationCache.get(cacheKey);
				if (view == null) {
					// Ask the subclass to create the View object.
					view = createView(viewName, locale);
					if (view == null && this.cacheUnresolved) {
						view = UNRESOLVED_VIEW;
					}
					if (view != null) {
						this.viewAccessCache.put(cacheKey, view);
						this.viewCreationCache.put(cacheKey, view);
					}
				}
			}
		}
		else {
			if (logger.isTraceEnabled()) {
				logger.trace(formatKey(cacheKey) + "served from cache");
			}
		}
		return (view != UNRESOLVED_VIEW ? view : null);
	}
}

createView 方法在子类 UrlBasedViewResolver 中实现。

// UrlBasedViewResolver.java
protected View createView(String viewName, Locale locale) throws Exception {
	// If this resolver is not supposed to handle the given view,
	// return null to pass on to the next resolver in the chain.
	// 如果当前解析器不支持解析,如 viewName 为空的情况
	if (!canHandle(viewName, locale)) {
		return null;
	}

	// Check for special "redirect:" prefix.
	// 处理前缀为 redirect 的情况,重定向
	if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
		String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
		RedirectView view = new RedirectView(redirectUrl,
				isRedirectContextRelative(), isRedirectHttp10Compatible());
		String[] hosts = getRedirectHosts();
		if (hosts != null) {
			view.setHosts(hosts);
		}
		return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
	}

	// Check for special "forward:" prefix.
	// 处理前缀为 forward 的情况,跳转
	if (viewName.startsWith(FORWARD_URL_PREFIX)) {
		String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
		InternalResourceView view = new InternalResourceView(forwardUrl);
		return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
	}

	// Else fall back to superclass implementation: calling loadView.
	// 调用 AbstractCachingViewResolver 的方法
	return super.createView(viewName, locale);
}

super.createView(viewName, locale)

// AbstractCachingViewResolver.java
protected View createView(String viewName, Locale locale) throws Exception {
	return loadView(viewName, locale);
}

// UrlBasedViewResolver.java 
protected View loadView(String viewName, Locale locale) throws Exception {
	AbstractUrlBasedView view = buildView(viewName);
	View result = applyLifecycleMethods(viewName, view);
	return (view.checkResource(locale) ? result : null);
}

buildView 方法。

// InternalResourceViewResolver.java
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
	// 首先调用父类 UrlBasedViewResolver 方法
	InternalResourceView view = (InternalResourceView) super.buildView(viewName);
	if (this.alwaysInclude != null) {
		view.setAlwaysInclude(this.alwaysInclude);
	}
	view.setPreventDispatchLoop(true);
	return view;
}

// UrlBasedViewResolver.java 
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
	Class<?> viewClass = getViewClass();
	Assert.state(viewClass != null, "No view class");

	// 实例化 AbstractUrlBasedView
	AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
	// 添加前缀和后缀
	view.setUrl(getPrefix() + viewName + getSuffix());

	String contentType = getContentType();
	if (contentType != null) {
		// 设置 ContentType
		view.setContentType(contentType);
	}

	view.setRequestContextAttribute(getRequestContextAttribute());
	view.setAttributesMap(getAttributesMap());

	Boolean exposePathVariables = getExposePathVariables();
	if (exposePathVariables != null) {
		view.setExposePathVariables(exposePathVariables);
	}
	Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
	if (exposeContextBeansAsAttributes != null) {
		view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
	}
	String[] exposedContextBeanNames = getExposedContextBeanNames();
	if (exposedContextBeanNames != null) {
		view.setExposedContextBeanNames(exposedContextBeanNames);
	}

	return view;
}

对于 InternalResourceViewResolver 所提供的解析功能主要考虑到

  1. 基于效率的考虑,提供缓存机制。
  2. 提供对 redirect:xx 和 forward:xx 的支持。
  3. 添加了前缀和后缀,并向 view 中加入了必须的属性设置。

2)页面跳转
当提供 viewName 解析获得对于的 View 后,就可以进一步的处理跳转逻辑了。

// AbstractView.java
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
	HttpServletResponse response) throws Exception {

	if (logger.isDebugEnabled()) {
		logger.debug("View " + formatViewName() +
				", model " + (model != null ? model : Collections.emptyMap()) +
				(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
	}

	// 解析 model 中的变量值,JSTL 等语法
	Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
	prepareResponse(request, response);
	// 处理跳转页面
	renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

在 ModelAndView 中,我们可以将一些属性直接放入其中,然后在页面上直接通过 JSTL 语法或者原始 request 获取。解析这些属性的工作是在 createMergedOutputModel 方法中实现。

protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
	HttpServletRequest request, HttpServletResponse response) {

	@SuppressWarnings("unchecked")
	Map<String, Object> pathVars = (this.exposePathVariables ?
			(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

	// Consolidate static and dynamic model attributes.
	int size = this.staticAttributes.size();
	size += (model != null ? model.size() : 0);
	size += (pathVars != null ? pathVars.size() : 0);

	Map<String, Object> mergedModel = new LinkedHashMap<>(size);
	mergedModel.putAll(this.staticAttributes);
	if (pathVars != null) {
		mergedModel.putAll(pathVars);
	}
	if (model != null) {
		mergedModel.putAll(model);
	}

	// Expose RequestContext?
	if (this.requestContextAttribute != null) {
		mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
	}

	return mergedModel;
}

处理页面的跳转。

// InternalResourceView.java
protected void renderMergedOutputModel(
	Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

	// Expose the model object as request attributes.
	// 将 model 中的属性设置到 request 中
	exposeModelAsRequestAttributes(model, request);

	// Expose helpers as request attributes, if any.
	exposeHelpers(request);

	// Determine the path for the request dispatcher.
	String dispatcherPath = prepareForRendering(request, response);

	// Obtain a RequestDispatcher for the target resource (typically a JSP).
	// 请求跳转
	RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
	if (rd == null) {
		throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
				"]: Check that the corresponding file exists within your web application archive!");
	}

	// If already included or response already committed, perform include, else forward.
	if (useInclude(request, response)) {
		response.setContentType(getContentType());
		if (logger.isDebugEnabled()) {
			logger.debug("Including [" + getUrl() + "]");
		}
		rd.include(request, response);
	}

	else {
		// Note: The forwarded resource is supposed to determine the content type itself.
		if (logger.isDebugEnabled()) {
			logger.debug("Forwarding to [" + getUrl() + "]");
		}
		// 跳转
		rd.forward(request, response);
	}
}