该系列文档是本人在学习 Spring MVC 的源码过程当中总结下来的,可能对读者不太友好,请结合个人源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读html
Spring 版本:5.2.4.RELEASEjava
随着 Spring Boot
和 Spring Cloud
在许多中大型企业中被普及,可能你已经忘记当年经典的 Servlet + Spring MVC 的组合,是否还记得那个 web.xml
配置文件。在开始本文以前,请先抛开 Spring Boot
到一旁,回到从前,一块儿来看看 Servlet 是怎么和 Spring MVC 集成,怎么来初始化 Spring 容器的,在开始阅读本文以前,最好有必定的 Servlet 和 Spring IOC 容器方面的知识,比较容易理解git
在开始看具体的源码实现以前,咱们先一块儿来看看如今“陌生”的 web.xml
文件,能够查看个人另外一篇 MyBatis 使用手册 文档中集成 Spring小节涉及到的 web.xml
的文件,部份内容以下:github
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>Archetype Created Web Application</display-name> <!-- 【1】 Spring 配置 --> <!-- 在容器(Tomcat、Jetty)启动时会被 ContextLoaderListener 监听到, 从而调用其 contextInitialized() 方法,初始化 Root WebApplicationContext 容器 --> <!-- 声明 Spring Web 容器监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Spring 和 MyBatis 的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mybatis.xml</param-value> </context-param> <!-- 【2】 Spring MVC 配置 --> <!-- 1.SpringMVC 配置 前置控制器(SpringMVC 的入口) DispatcherServlet 是一个 Servlet,因此能够配置多个 DispatcherServlet --> <servlet> <!-- 在 DispatcherServlet 的初始化过程当中,框架会在 web 应用 的 WEB-INF 文件夹下, 寻找名为 [servlet-name]-servlet.xml 的配置文件,生成文件中定义的 Bean. --> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置须要加载的配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <!-- 程序运行时从 web.xml 开始,加载顺序为:context-param -> Listener -> Filter -> Structs -> Servlet 设置 web.xml 文件启动时加载的顺序(1 表明容器启动时首先初始化该 Servlet,让这个 Servlet 随 Servlet 容器一块儿启动) load-on-startup 是指这个 Servlet 是在当前 web 应用被加载的时候就被建立,而不是第一次被请求的时候被建立 --> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <!-- 这个 Servlet 的名字是 SpringMVC,能够有多个 DispatcherServlet,是经过名字来区分的 每个 DispatcherServlet 有本身的 WebApplicationContext 上下文对象,同时保存在 ServletContext 中和 Request 对象中 ApplicationContext(Spring 容器)是 Spring 的核心 Context 咱们一般解释为上下文环境,Spring 把 Bean 放在这个容器中,在须要的时候,能够 getBean 方法取出--> <servlet-name>SpringMVC</servlet-name> <!-- Servlet 拦截匹配规则,可选配置:*.do、*.action、*.html、/、/xxx/* ,不容许:/* --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
【1】
处,配置了 org.springframework.web.context.ContextLoaderListener
对象,它实现了 Servlet 的 javax.servlet.ServletContextListener
接口,可以监听 ServletContext 对象的生命周期,也就是监听 Web 应用的生命周期,当 Servlet 容器启动或者销毁时,会触发相应的 ServletContextEvent 事件,ContextLoaderListener 监听到启动事件,则会初始化一个Root Spring WebApplicationContext 容器,监听到销毁事件,则会销毁该容器web
【2】
处,配置了 org.springframework.web.servlet.DispatcherServlet
对象,它继承了 javax.servlet.http.HttpServlet
抽象类,也就是一个 Servlet。Spring MVC 的核心类,处理请求,会初始化一个属于它的 Spring WebApplicationContext 容器,而且这个容器是以 【1】
处的 Root 容器做为父容器spring
【2】
建立了容器,还须要 【1】
建立 Root 容器呢?由于能够配置多个 【2】
呀,固然,实际场景下,不太会配置多个 【2】
😈【1】
和 【2】
分别会建立其对应的 Spring WebApplicationContext 容器,而且它们是父子容器的关系在概述的 web.xml
中,咱们已经看到,Root WebApplicationContext 容器的初始化,经过 ContextLoaderListener
来实现。在 Servlet 容器启动时,例如 Tomcat、Jetty 启动后,则会被 ContextLoaderListener
监听到,从而调用 contextInitialized(ServletContextEvent event)
方法,初始化 Root WebApplicationContext 容器spring-mvc
而 ContextLoaderListener 的类图以下:mybatis
org.springframework.web.context.ContextLoaderListener
类,实现 javax.servlet.ServletContextListener
接口,继承 ContextLoader 类,实现 Servlet 容器启动和关闭时,分别初始化和销毁 WebApplicationContext 容器,代码以下:mvc
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } /** * As of Spring 3.1, supports injecting the root web application context */ public ContextLoaderListener(WebApplicationContext context) { super(context); } /** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { // <1> 初始化 Root WebApplicationContext initWebApplicationContext(event.getServletContext()); } /** * Close the root web application context. */ @Override public void contextDestroyed(ServletContextEvent event) { // <2> 销毁 Root WebApplicationContext closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
initWebApplicationContext(ServletContext servletContext)
方法,初始化 WebApplicationContext 容器closeWebApplicationContext(ServletContext servletContext)
方法,销毁 WebApplicationContext 容器org.springframework.web.context.ContextLoader
类,真正实现初始化和销毁 WebApplicationContext 容器的逻辑的类app
public class ContextLoader { /** * Name of the class path resource (relative to the ContextLoader class) * that defines ContextLoader's default strategy names. */ private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; /** * 默认的配置 Properties 对象 */ private static final Properties 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, ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } } }
从 ContextLoader.properties
中,读取默认的配置 Properties 对象。实际上,正如 Load default strategy implementations from properties file. This is currently strictly internal and not meant to be customized by application developers.
所注释,这是一个应用开发者无需关心的配置,而是 Spring 框架自身所定义的
打开来该文件瞅瞅,代码以下:
# Default WebApplicationContext implementation class for ContextLoader. # Used as fallback when no explicit context implementation has been specified as context-param. # Not meant to be customized by application developers. org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
这意味着什么呢?若是咱们没有在 <context-param />
标签中指定 WebApplicationContext,则默认使用 XmlWebApplicationContext 类,咱们在使用 Spring 的过程当中通常状况下不会主动指定
public class ContextLoader { /** * Name of servlet context parameter (i.e., {@value}) that can specify the * config location for the root context, falling back to the implementation's default otherwise. * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION */ public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; /** Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext. */ private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread = new ConcurrentHashMap<>(1); /** The 'current' WebApplicationContext, if the ContextLoader class is deployed in the web app ClassLoader itself. */ @Nullable private static volatile WebApplicationContext currentContext; /** The root WebApplicationContext instance that this loader manages. */ @Nullable private WebApplicationContext context; /** * Create a new {@code ContextLoader} that will create a web application context * based on the "contextClass" and "contextConfigLocation" servlet context-params. * See class-level documentation for details on default values for each. */ public ContextLoader() { } /** * Create a new {@code ContextLoader} with the given application context. * This constructor is useful in Servlet 3.0+ environments where instance-based * registration of listeners is possible through the {@link ServletContext#addListener} API. */ public ContextLoader(WebApplicationContext context) { this.context = context; } // ... 省略其余相关配置属性 }
web.xml
文件中能够看到定义的 contextConfigLocation
参数为 spring-mybatis.xml 配置文件路径currentContextPerThread
:用于保存当前 ClassLoader 类加载器与 WebApplicationContext 对象的映射关系currentContext
:若是当前线程的类加载器就是 ContextLoader 类所在的类加载器,则该属性用于保存 WebApplicationContext 对象context
:WebApplicationContext 实例对象关于类加载器涉及到 JVM 的“双亲委派机制”,在《精尽MyBatis源码分析 - 基础支持层》 有简单的讲述到,能够参考一下
initWebApplicationContext(ServletContext servletContext)
方法,初始化 WebApplicationContext 对象,代码以下:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // <1> 若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。 // 例如,在 web.xml 中存在多个 ContextLoader 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!"); } // <2> 打印日志 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) { // <3> 初始化 context ,即建立 context 对象 this.context = createWebApplicationContext(servletContext); } // <4> 若是是 ConfigurableWebApplicationContext 的子类,若是未刷新,则进行配置和刷新 if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { // <4.1> 未刷新( 激活 ) // 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) { // <4.2> 无父容器,则进行加载和设置。 // 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); } // <4.3> 配置 context 对象,并进行刷新 configureAndRefreshWebApplicationContext(cwac, servletContext); } } // <5> 记录在 servletContext 中 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); // <6> 记录到 currentContext 或 currentContextPerThread 中 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms"); } // <7> 返回 context return this.context; } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } }
若 ServletContext(Servlet 的上下文)已存在 Root WebApplicationContext 对象,则抛出异常,由于不能再初始化该对象
打印日志,在启动 SSM 项目的时候,是否是都会看到这个日志“Initializing Spring root WebApplicationContext”
若是context
为空,则调用createWebApplicationContext(ServletContext sc)
方法,初始化一个 Root WebApplicationContext 对象,方法以下:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // <1> 得到 context 的类(默认状况是从 ContextLoader.properties 配置文件读取的,为 XmlWebApplicationContext) Class<?> contextClass = determineContextClass(sc); // <2> 判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } // <3> 建立 context 的类的对象 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
若是是 ConfigurableWebApplicationContext 的子类,而且未刷新,则进行配置和刷新
loadParentContext(ServletContext servletContext)
方法返回一个空对象,也就是没有父容器了configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
方法,配置context
对象,并进行刷新将context
对象保存在 ServletContext 中
将context
对象设置到currentContext
或者currentContextPerThread
对象中,差别就是类加载器是否相同,具体用途目前不清楚😈
返回已经初始化的context
对象
configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
方法,配置 ConfigurableWebApplicationContext 对象,并进行刷新,方法以下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { // <1> 若是 wac 使用了默认编号,则从新设置 id 属性 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 // 状况一,使用 contextId 属性 String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // 状况二,自动生成 // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } // <2>设置 context 的 ServletContext 属性 wac.setServletContext(sc); // <3> 设置 context 的配置文件地址 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // 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(sc, null); } // <4> 对 context 进行定制化处理 customizeContext(sc, wac); // <5> 刷新 context ,执行初始化 wac.refresh(); }
若是 wac
使用了默认编号,则从新设置 id
属性。默认状况下,咱们不会对 wac
设置编号,因此会执行进去。而实际上,id
的生成规则,也分红使用 contextId
在 <context-param />
标签中由用户配置,和自动生成两种状况。😈 默认状况下,会走第二种状况
设置 wac
的 ServletContext 属性
【关键】设置 context
的配置文件地址。例如咱们在概述中的 web.xml
中所看到的
<!-- Spring 和 MyBatis 的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mybatis.xml</param-value> </context-param>
对 wac
进行定制化处理,暂时忽略
【关键】触发 wac
的刷新事件,执行初始化。此处,就会进行一些的 Spring 容器的初始化工做,涉及到 Spring IOC 相关内容
closeWebApplicationContext(ServletContext servletContext)
方法,关闭 WebApplicationContext 容器对象,方法以下:
public void closeWebApplicationContext(ServletContext servletContext) { servletContext.log("Closing Spring root WebApplicationContext"); try { // 关闭 context if (this.context instanceof ConfigurableWebApplicationContext) { ((ConfigurableWebApplicationContext) this.context).close(); } } finally { // 移除 currentContext 或 currentContextPerThread ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = null; } else if (ccl != null) { currentContextPerThread.remove(ccl); } // 从 ServletContext 中移除 servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); } }
在 Servlet 容器销毁时被调用,用于关闭 WebApplicationContext 对象,以及清理相关资源对象
在概述的 web.xml
中,咱们已经看到,除了会初始化一个 Root WebApplicationContext 容器外,还会往 Servlet 容器的 ServletContext 上下文中注入一个 DispatcherServlet
对象,初始化该对象的过程也会初始化一个 Servlet WebApplicationContext 容器
DispatcherServlet 的类图以下:
能够看到 DispatcherServlet
是一个 Servlet 对象,在注入至 Servlet 容器会调用其 init
方法,完成一些初始化工做
HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中,它的 Java doc:
/** * Simple extension of {@link javax.servlet.http.HttpServlet} which treats * its config parameters ({@code init-param} entries within the * {@code servlet} tag in {@code web.xml}) as bean properties. */
FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器,同时该类覆写了 doGet、doPost 等方法,并将全部类型的请求委托给 doService 方法去处理,doService 是一个抽象方法,须要子类实现,它的 Java doc:
/** * Base servlet for Spring's web framework. Provides integration with * a Spring application context, in a JavaBean-based overall solution. */
DispatcherServlet ,负责初始化 Spring MVC 的各个组件,以及处理客户端的请求,协调各个组件工做,它的 Java doc:
/** * Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers * or HTTP-based remote service exporters. Dispatches to registered handlers for processing * a web request, providing convenient mapping and exception handling facilities. */
每一层的 Servlet 实现类,负责执行相应的逻辑,条理清晰,咱们逐个来看
org.springframework.web.servlet.HttpServletBean
抽象类,实现 EnvironmentCapable、EnvironmentAware 接口,继承 HttpServlet 抽象类,负责将 ServletConfig 集成到 Spring 中
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { @Nullable private ConfigurableEnvironment environment; /** * 必须配置的属性的集合,在 {@link ServletConfigPropertyValues} 中,会校验是否有对应的属性 * 默认为空 */ private final Set<String> requiredProperties = new HashSet<>(4); protected final void addRequiredProperty(String property) { this.requiredProperties.add(property); } /** * 实现了 EnvironmentAware 接口,自动注入 Environment 对象 */ @Override public void setEnvironment(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "ConfigurableEnvironment required"); this.environment = (ConfigurableEnvironment) environment; } /** * 实现了 EnvironmentAware 接口,返回 Environment 对象 */ @Override public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { // 若是 Environment 为空,则建立 StandardServletEnvironment 对象 this.environment = createEnvironment(); } return this.environment; } /** * Create and return a new {@link StandardServletEnvironment}. */ protected ConfigurableEnvironment createEnvironment() { return new StandardServletEnvironment(); } }
关于 xxxAware接口,在 Spring 初始化该 Bean 的时候会调用其setXxx方法来注入一个对象,本文暂不分析
init()
方法,重写 GenericServlet 中的方法,负责将 ServletConfig 设置到当前 Servlet 对象中,方法以下:
@Override public final void init() throws ServletException { // Set bean properties from init parameters. // <1> 解析 <init-param /> 标签,封装到 PropertyValues pvs 中 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { // <2.1> 将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而可以以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); // <2.2> 注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); // <2.3> 空实现,留给子类覆盖,目前没有子类实现 initBeanWrapper(bw); // <2.4> 以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中 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. // 交由子类去实现,查看 FrameworkServlet#initServletBean() 方法 initServletBean(); }
解析 Servlet 配置的 <init-param />
标签,封装成 PropertyValues pvs
对象。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类,ServletConfig 的 封装实现类,该类的代码以下:
private static class ServletConfigPropertyValues extends MutablePropertyValues { public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException { // 得到缺失的属性的集合 Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? new HashSet<>(requiredProperties) : null); // <1> 遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中,并从 missingProps 移除 Enumeration<String> paramNames = config.getInitParameterNames(); while (paramNames.hasMoreElements()) { String property = paramNames.nextElement(); Object value = config.getInitParameter(property); // 添加到 ServletConfigPropertyValues 中 addPropertyValue(new PropertyValue(property, value)); // 从 missingProps 中移除 if (missingProps != null) { missingProps.remove(property); } } // Fail if we are still missing properties. if (!CollectionUtils.isEmpty(missingProps)) { throw new ServletException("..."); } } }
在它的构造方法中能够看到,将<init-param />
标签订义的一些配置项解析成 PropertyValue 对象,例如在前面概述的web.xml
中的配置,以下:
<init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param>
若是存在<init-param />
初始化参数
pvs
注入到该 BeanWrapper 对象中。简单来讲,BeanWrapper 是 Spring 提供的一个用来操做 Java Bean 属性的工具,使用它能够直接修改一个对象的属性initBeanWrapper(BeanWrapper bw)
方法,可初始化当前这个 Servlet 对象,空实现,留给子类覆盖,目前好像尚未子类实现pvs
中的属性值,注入到该 BeanWrapper 对象中,也就是设置到当前 Servlet 对象中,例如 FrameworkServlet 中的 contextConfigLocation
属性则会设置为上面的 classpath:spring-mvc.xml
值了【关键】调用initServletBean()
方法,空实现,交由子类去实现,完成自定义初始化逻辑,查看 FrameworkServlet#initServletBean()
方法
org.springframework.web.servlet.FrameworkServlet
抽象类,实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类,负责初始化 Spring Servlet WebApplicationContext 容器
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { // ... 省略部分属性 /** Default context class for FrameworkServlet. */ public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; /** WebApplicationContext implementation class to create. */ private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; /** Explicit context config location. 配置文件的地址 */ @Nullable private String contextConfigLocation; /** Should we publish the context as a ServletContext attribute?. */ private boolean publishContext = true; /** Should we publish a ServletRequestHandledEvent at the end of each request?. */ private boolean publishEvents = true; /** WebApplicationContext for this servlet. */ @Nullable private WebApplicationContext webApplicationContext; /** 标记是不是经过 {@link #setApplicationContext} 注入的 WebApplicationContext */ private boolean webApplicationContextInjected = false; /** 标记已是否接收到 ContextRefreshedEvent 事件,即 {@link #onApplicationEvent(ContextRefreshedEvent)} */ private volatile boolean refreshEventReceived = false; /** Monitor for synchronized onRefresh execution. */ private final Object onRefreshMonitor = new Object(); public FrameworkServlet() { } public FrameworkServlet(WebApplicationContext webApplicationContext) { this.webApplicationContext = webApplicationContext; } @Override public void setApplicationContext(ApplicationContext applicationContext) { if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) { this.webApplicationContext = (WebApplicationContext) applicationContext; this.webApplicationContextInjected = true; } } }
contextClass
属性:建立的 WebApplicationContext 类型,默认为 XmlWebApplicationContext.class,在 Root WebApplicationContext 容器的建立过程当中也是它
contextConfigLocation
属性:配置文件的地址,例如:classpath:spring-mvc.xml
webApplicationContext
属性:WebApplicationContext 对象,即本文的关键,Servlet WebApplicationContext 容器,有四种建立方式
setApplicationContext(ApplicationContext applicationContext)
方法findWebApplicationContext()
方法,下文见createWebApplicationContext(WebApplicationContext parent)
方法,下文见initServletBean()
方法,重写父类的方法,在 HttpServletBean 的 init() 方法的最后一步会调用,进一步初始化当前 Servlet 对象,当前主要是初始化Servlet WebApplicationContext 容器,代码以下:
@Override 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 { // <1> 初始化 WebApplicationContext 对象 this.webApplicationContext = initWebApplicationContext(); // <2> 空实现,留给子类覆盖,目前没有子类实现 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"); } }
initWebApplicationContext()
方法,初始化 Servlet WebApplicationContext 对象initFrameworkServlet()
方法,可对当前 Servlet 对象进行自定义操做,空实现,留给子类覆盖,目前好像尚未子类实现initWebApplicationContext()
方法【核心】,初始化 Servlet WebApplicationContext 对象,方法以下:
protected WebApplicationContext initWebApplicationContext() { // <1> 得到根 WebApplicationContext 对象 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); // <2> 得到 WebApplicationContext wac 对象 WebApplicationContext wac = null; // 第一种状况,若是构造方法已经传入 webApplicationContext 属性,则直接使用 if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; // 若是是 ConfigurableWebApplicationContext 类型,而且未激活,则进行初始化 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); } // 配置和初始化 wac configureAndRefreshWebApplicationContext(cwac); } } } // 第二种状况,从 ServletContext 获取对应的 WebApplicationContext 对象 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 wac = findWebApplicationContext(); } // 第三种,建立一个 WebApplicationContext 对象 if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } // <3> 若是未触发刷新事件,则主动触发刷新事件 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); } } // <4> 将 context 设置到 ServletContext 中 if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
调用 WebApplicationContextUtils#getWebApplicationContext((ServletContext sc)
方法,从 ServletContext 中得到 Root WebApplicationContext 对象,能够回到ContextLoader#initWebApplicationContext方法中的第 5
步,你会以为很熟悉
得到 WebApplicationContext wac
对象,有三种状况
若是构造方法已经传入 webApplicationContext 属性,则直接引用给 wac
,也就是上面构造方法中提到的第 一、2 种建立方式
若是 wac
是 ConfigurableWebApplicationContext 类型,而且未刷新(未激活),则调用 configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)
方法,进行配置和刷新,下文见
若是父容器为空,则设置为上面第 1
步获取到的 Root WebApplicationContext 对象
调用 findWebApplicationContext()
方法,从 ServletContext 获取对应的 WebApplicationContext 对象,也就是上面构造方法中提到的第 3 种建立方式
@Nullable protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); // 须要配置了 contextAttribute 属性下,才会去查找,通常咱们不会去配置 if (attrName == null) { return null; } // 从 ServletContext 中,得到属性名对应的 WebApplicationContext 对象 WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); // 若是不存在,则抛出 IllegalStateException 异常 if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; }
通常不会这样作
调用createWebApplicationContext(@Nullable WebApplicationContext parent)
方法,建立一个 WebApplicationContext 对象
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { // <a> 得到 context 的类,XmlWebApplicationContext.class Class<?> contextClass = getContextClass(); // 若是非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常 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"); } // <b> 建立 context 类的对象 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); // <c> 设置 environment、parent、configLocation 属性 wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } // <d> 配置和初始化 wac configureAndRefreshWebApplicationContext(wac); return wac; }
<a>
得到 context
的 Class 对象,默认为 XmlWebApplicationContext.class,若是非 ConfigurableWebApplicationContext 类型,则抛出异常
<b>
建立 context
的实例对象
<c>
设置 environment
、parent
、configLocation
属性。其中,configLocation
是个重要属性
<d>
调用 configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)
方法,进行配置和刷新,下文见
若是未触发刷新事件,则调用 onRefresh(ApplicationContext context)
方法,主动触发刷新事件,该方法为空实现,交由子类 DispatcherServlet 去实现
将 context
设置到 ServletContext 中
configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)
方法,配置和初始化 wac
对象,方法以下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { // <1> 若是 wac 使用了默认编号,则从新设置 id 属性 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 // 状况一,使用 contextId 属性 if (this.contextId != null) { wac.setId(this.contextId); } // 状况二,自动生成 else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } // <2> 设置 wac 的 servletContext、servletConfig、namespace 属性 wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); // <3> 添加监听器 SourceFilteringListener 到 wac 中 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 // <4> ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } // <5> 执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现 postProcessWebApplicationContext(wac); // <6> 执行自定义初始化 context applyInitializers(wac); // <7> 刷新 wac ,从而初始化 wac wac.refresh(); }
实际上,处理逻辑和ContextLoader#configureAndRefreshWebApplicationContext
方法差很少
wac
使用了默认编号,则从新设置 id
属性wac
的 servletContext、servletConfig、namespace 属性wac
中wac
进行定制化处理,暂时忽略wac
的刷新事件,执行初始化。此处,就会进行一些的 Spring 容器的初始化工做,涉及到 Spring IOC 相关内容onRefresh(ApplicationContext context)
方法,当 Servlet WebApplicationContext 刷新完成后,触发 Spring MVC 组件的初始化,方法以下:
/** * Template method which can be overridden to add servlet-specific refresh work. * Called after successful context refresh. * <p>This implementation is empty. * @param context the current WebApplicationContext * @see #refresh() */ protected void onRefresh(ApplicationContext context) { // For subclasses: do nothing by default. }
这是一个空方法,具体的实现,在子类 DispatcherServlet 中,代码以下:
// DispatcherServlet.java @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } /** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ 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); }
初始化九个组件,这里只是先提一下,在后续的文档中会进行分析
onRefresh
方法的触发有两种方式:
refreshEventReceived
为 false
,也就是未接收到刷新事件(防止重复初始化相关组件),则在 initWebApplicationContext
方法中直接调用configureAndRefreshWebApplicationContext
方法中,触发 wac
的刷新事件为何上面的方式二能够触发这个方法的调用呢?
先看到 configureAndRefreshWebApplicationContext
方法的第 3
步,添加了一个 SourceFilteringListener 监听器,以下:
// <3> 添加监听器 SourceFilteringListener 到 wac 中 wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
监听到相关事件后,会委派给 ContextRefreshListener
进行处理,它是 FrameworkServlet 的私有内部类,来看看它又是怎么处理的,代码以下:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { FrameworkServlet.this.onApplicationEvent(event); } }
直接将该事件委派给了 FrameworkServlet 的 onApplicationEvent
方法,以下:
public void onApplicationEvent(ContextRefreshedEvent event) { // 标记 refreshEventReceived 为 true this.refreshEventReceived = true; synchronized (this.onRefreshMonitor) { // 处理事件中的 ApplicationContext 对象,空实现,子类 DispatcherServlet 会实现 onRefresh(event.getApplicationContext()); } }
先设置 refreshEventReceived
为 true
,表示已接收到刷新时间,而后再调用 onRefresh
方法,回到上面的方式一和方式二,是否是连通起来了,因此说该方法是必定会被触发的
本分对 Spring MVC 两种容器的建立过程进行分析,分别为 Root WebApplicationContext 和 Servlet WebApplicationContext 容器,它们是父子关系,建立过程并非很复杂。前置是在 Tomcat 或者 Jetty 等 Servlet 容器启动后,由 ContextLoaderListener
监听到相应事件而建立的,后者是在 DispatcherServlet
初始化的过程当中建立的,由于它是一个 HttpServlet 对象,会调用其 init
方法,完成初始化相关工做
DispatcherServlet
是 Spring MVC 的核心类,至关于一个调度者,请求的处理过程都是经过它调度各个组件来完成的,在后续的文章中进行分析
参考文章:芋道源码《精尽 Spring MVC 源码分析》