Spring的啟動過程,就是其IoC容器的啟動過程,本質就是建立和初始化bean的工廠(BeanFactory),BeanFactory其實就是整個SpringIoc的核心,Spring 使用 BeanFactory 來例項化、配置和管理 Bean。
對於web程式,IoC容器啟動過程即是建立上下文的過程,在web應用中,web容器會提供一個全域性的ServletContext上下文環境,ServletContext上下文為Spring IoC提供了一個宿主環境。
Spring應用在Web容器中啟動的整個過程如下:
先介紹下原始碼中出現的各位小夥伴:
- Resource:是spring對於資源的一種抽象,因為資源的來源可能很豐富,利於File,Class Path Resource,Url Resource等,進行統一封裝,暴露出getInputStream進行統一讀取解析
- Document:這個沒啥好講的,XML文件物件
- EncodedResource:封裝了Resource,指定Resource的編碼
- ReaderContext:Bean Definition解析過程中的上下文物件,封裝了Resource、ProblemReporter、EventListener、SourceExtractor等
- Element:XML中的元素節點物件
- BeanDefinition:這個介面及其實現類非常重要,他描述了XML中一個bean節點及其子節點的所有屬性,將xml中的描述轉變成內部的field資訊,舉例:scope,lazyinit,ConstructorArgumentValues(描述構造器),PropertyValues(描述屬性值)等,是一個保羅永珍的介面,其子類實現包括GenericBeanDefinition、RootBeanDefinition、ChildBeanDefinition等
- BeanDefinitionHolder:顧名思義包含了一個BeanDefinition,同時其包含了beanName和aliases,更好的封裝了一次
- BeanDefinitionReader:定義了讀取BeanDefinition的介面,主要作用是從Resource中讀取Bean定義,XmlBeanDefinitionReader是其具體的實現類
- BeanDefinitionDocumentReader:定義了從Document物件中解析BeanDefinition並且註冊到Registry中設計到的介面,其預設實現類是DefaultBeanDefinitionDocumentReader,主要是被XmlBeanDefinitionReader委派去處理Document物件
- BeanDefinitionParserDelegate:看到Delegate就知道這個哥們是個受苦的物件,是個最終幹活的人,官方定義是“Stateful delegate class used to parse XML bean definitions.* Intended for use by both the main parser and any extension* {@link BeanDefinitionParser BeanDefinitionParsers} or* {@link BeanDefinitionDecorator BeanDefinitionDecorators}.”是用於最終處理XML bean定義的人,它做的可都是髒活累活,import/alias/bean等element以及element的子節點以及屬性都是它解析並且填充到BeanDefinition中然後使用ReaderContext中的Registry(實際就是DefaultListableBeanFactory)來將該BeanDefinition註冊
在Web專案使用Spring,是通過在web.xml裡面配置:
org.springframework.web.context.ContextLoaderListener
來初始化IOC容器的。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>複製程式碼
確切的說,ContextLoaderListener這個監聽物件,監聽的是ServletContext這個,當web容器初始化,ServletContext發生變化的時候,會觸發相應的事件。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener複製程式碼
ContextLoaderListener繼承了ContextLoader,並實現了ServletContextListener介面,在web容器初始化的時候,會觸發ServletContextListener介面中的contextInitialized()方法,同理,在容器關閉的時候,會觸發對應的contextDestroyed()方法。
其中,initWebApplicationContext()方法為父類ContextLoader中的方法:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)複製程式碼
由於initWebApplicationContext()方法原始碼較長,我們進行拆分閱讀。
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!");
}
//日誌記錄
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
//啟動startTime,記錄啟動耗時
long startTime = System.currentTimeMillis();複製程式碼
首先判斷是否建立了WebApplicationContext,正常情況下建立了一個WebApplicationContext後,會把context set到ServletContext中,setAttribute的本質其實是往LinkedHashMap中set值。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);複製程式碼
如果從servletContext中根據key獲得了WebApplicationContext物件,則表示之前已經建立過了根上下文WebApplicationContext,此時丟擲異常,提示檢查web.xml中的配置,避免重複建立root上下文,保證只有一個Spring容器。
再看下面的程式碼:
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
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.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.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}複製程式碼
程式碼片段較長,一步一步來,try中有倆關鍵的地方:
createWebApplicationContext()和configureAndRefreshWebApplicationContext()
先逐步往下看
首先是判斷 this.context == null,通過createWebApplicationContext方法建立一個
WebApplicationContext,具體程式碼如下:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
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);
}複製程式碼
determineContextClass,字面意思為檢測Context的class型別,會讀取servletContext的初始化引數contextClass,大部分情況下我們不會配置此引數,在未配置的情況下,Spring會去org.springframework.web.context包中的ContextLoader.properties配置檔案讀取預設配置:
通過Spring提供的ClassUtil進行反射,反射出XmlWebApplicationContext類,再通過BeanUtils進行反射,呼叫無參構造器,instance出一個WebApplicationContext並返回ConfigurableWebApplicationContext。
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);
}
}複製程式碼
獲得WebApplicationContext後,下一步判斷獲得的context是否為
ConfigurableWebApplicationContext的例項,預設的XmlWebApplicationContext滿足判斷條件。
判斷父上下文是否為active狀態,如果active為false下,需要判斷父上下文是否為null,如果parent上下文為null的情況,則執行loadParentContext()。loadParentContext()方法,是一個預設的是模板實現方法,用於載入/獲取ApplicationContext,此context為根WebApplicationContext的父上下文。
下一步,進入configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)複製程式碼
將上面建立的XmlWebApplicationContext進行初始化操作。
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
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()));
}
}複製程式碼
主要建立一個預設id,org.springframework.web.context.WebApplicationContext:
+專案的ContextPath
//設定sc到wac中,便於Spring獲得ServletContext
wac.setServletContext(sc);
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);
}
customizeContext(sc, wac);
wac.refresh();複製程式碼
建立好id後,把servletContext放入建立好的XmlWebApplicationContext中,便於spring後續獲得servletContext上下文。
緊接著會去讀取web.xml中<param-name>contextConfigLocation</param-name>的配置
如果在web.xml中未配置,則會去讀取WEB-INF下面的applicationContext.xml配置檔案,
即XmlWebApplicationContext中的屬性 DEFAULT_CONFIG_LOCATION的預設值
/** Default config location for the root context */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";複製程式碼
讀取到contextConfigLocation相關檔案配置路徑後,設定到XmlWebApplicationContext中,用於載入指定路徑的配置檔案。
下一步 customizeContext(sc, wac);
主要用於自定義context相關配置,比如定義bean是否可以迴圈引用,bean定義是否可以被覆蓋等,通常情況下不做任何操作。
最後一步:
wac.refresh();複製程式碼
整個容器啟動的最核心方法,在這個refresh()方法中,會完成資原始檔的載入、配置檔案解析、Bean定義的註冊、元件的初始化等核心工作。
refresh()方法的相關邏輯,下一篇繼續深入學習。
參考連結:https://www.jianshu.com/p/375ef7095139