聲明
1.建議先閱讀《Spring源碼分析專題 —— 閱讀指引》
2.強烈建議閱讀過程當中要參照調用過程圖,每篇都有其對應的調用過程圖
3.寫文不易,轉載請標明出處html
關於 IOC 容器啓動的內容不少,我將分上中下三篇講解,其中上篇相對簡單,中篇最爲複雜,請你們耐心閱讀。web
因爲篇幅問題,此處我只放個縮略圖,高清大圖請點擊連接☞ IOC容器啓動調用過程圖.jpg
請務必一邊對照圖片一邊閱讀文章。
spring
此處先放結論,你們稍微記一記,後邊將展開詳解spring-mvc
咱們知道 Spring 框架不單單是面向 Web 應用,因此 Spring 中對應不一樣場景有許多 IOC 容器的實現類,其中有簡單的也有複雜的,在此咱們跳過簡單容器的講解,直接以咱們最熟悉、也是最感興趣的 Java Web 項目下手,尋找其對應的 IOC 容器實現類,同時一口氣尋找到 IOC 容器的關鍵入口方法 refresh() 。mvc
如下是咱們熟知的 SpringMVC 項目中 web.xml 的基礎配置,其關鍵是要配置一個 ContextLoaderListener 和一個 DispatcherServletapp
<web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <!-- ContextLoaderListener --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- DispatcherServlet --> <servlet> <description>spring mvc servlet</description> <servlet-name>springMvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <description>spring mvc</description> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
咱們知道在 Java Web 容器中相關組件的啓動順序是 ServletContext -> listener -> filter -> servlet , listener 是優於 servlet 啓動的,因此咱們先看一看 ContextLoaderListener 的內容框架
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } public void contextInitialized(ServletContextEvent event) { this.initWebApplicationContext(event.getServletContext()); } public void contextDestroyed(ServletContextEvent event) { this.closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
根據 Java Web 容器的規範可知,當 Listener 啓動時會調用 contextInitialized 方法,而 ContextLoaderListener 中該方法的內容是繼續調用 initWebApplicationContext 方法,因而咱們再跟蹤 initWebApplicationContext
( ContextLoaderListener 是 ContextLoader 的子類,因此實際上是調用了父類的 initWebApplicationContext 方法)ide
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 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!"); } else { Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis(); try { if (this.context == null) { this.context = this.createWebApplicationContext(servletContext); } . . . }
此處咱們關心的是 createWebApplicationContext 方法源碼分析
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { /** [note-by-leapmie] determineContextClass方法中獲取contextClass **/ Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } /** [note-by-leapmie] 根據contextClass返回實例 */ return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
從代碼可知,方法中的邏輯主要是調用 determineContextClass 獲取 contextClass ,而後根據 contextClass 建立 IOC 容器實例。因此, contextClass 的值將是關鍵。post
protected Class<?> determineContextClass(ServletContext servletContext) { String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { . . . } else { /** * [note-by-leapmie] * defaultStrategies的值是在本類中的static方法中注入的 * 即該類加載過程當中defaultStrategies已經被賦值 * 本類的開始部分有static代碼塊 * **/ 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); } } }
能夠看到, contextClassName 是從 defaultStrategies 中獲取的,而關於 defaultStrategies 的賦值須要追溯到 ContextLoader 類中的靜態代碼塊
static { try { /** * [note-by-leapmie] * DEFAULT_STRATEGIES_PATH的值是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()); } }
defaultStrategies 是從 resource 中獲取的參數,而 resource 又是從 DEFAULT_STRATEGIES_PATH 中獲取,查看可知 DEFAULT_STRATEGIES_PATH 的值是 ContextLoader.properties ,經過全局查找到ContextLoader.properties文件,其中內容以下
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
由此可知, SpringMVC 項目中使用到的 IOC 容器類型是 XmlWebApplicationContext。
咱們回到 ContextLoader 的 initWebApplicationContext 方法,前邊咱們說到調用 createWebApplicationContext 方法建立容器,容器建立後咱們關注的下一個方法是 configureAndRefreshWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { . . . try { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. if (this.context == null) { /** [note-by-leapmie] 獲取SpringIOC容器類型 **/ 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); } /** [note-by-leapmie] 配置和刷新容器 **/ configureAndRefreshWebApplicationContext(cwac, servletContext); } } . . . }
configureAndRefreshWebApplicationContext的代碼以下
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { 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())); } } 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); /** [note-by-leapmie] 調用容器的refresh()方法,此處wac對應的類是XmlWebApplicationContext **/ wac.refresh(); }
在這裏咱們要關注的是最後一行 wac.refresh() ,意思是調用容器的 refresh() 方法,此處咱們的容器是XmlWebApplicationContext,對應的 refresh() 在其父類 AbstractApplicationContext
@Override /** 核心過程 **/ public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. /** * obtainFreshBeanFactory方法中會調用loadBeanDefinition方法,用於加載bean的定義 */ ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. /** 初始化全部非lazy-init的bean **/ finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
至此咱們已經找到了關鍵的入口 refresh() ,咱們看一下在調用過程圖中咱們所處的位置
refresh 方法是 Spring IOC 容器啓動過程的核心方法,方法中按順序調用了好幾個命名清晰的方法,其對應的都是 IOC 容器啓動過程的關鍵步驟,更多的細節咱們將在下一節繼續講解。
話癆一下
你們可能會以爲,在源碼分析過程當中一個方法中調用了不少方法,例如先執行方法 a() ,再執行方法 b() ,爲何咱們直接看方法 b() 而跳過了方法 a() ?
在這裏我想說的是,Spring的源碼量很龐大,若是每一個細節都去了解可能一年過去了都看不完,咱們應該先關注大流程,其餘的細枝末節能夠在瞭解了大流程後再慢慢深刻了解。
至於爲何是看方法 b() 而跳過方法 a() ,這些都是前人總結的經驗與心血,在學習過程當中我也是跟着別人的步伐在源碼中探索,中間有些缺失的路線我也花費大量時間去踩坑,最後繪製了每一份調用過程圖。在本專題中我能確保的是,只要跟着個人步伐,大家不會在源碼分析的路上迷路。
本文首發地址:https://blog.leapmie.com/archives/390/
[目錄]
[上一篇]Spring源碼分析專題 —— 閱讀指引
[下一篇]Spring源碼分析專題 —— IOC容器啓動過程(中篇)