該系列文檔是本人在學習 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 源碼分析》