前言
我們一般都知道怎樣使用spring來開發web應用后,但對spring的內部實現機制通常不是很明白。這里從源碼角度分析下Spring是怎樣啟動的。在講spring啟動之前,我們先來看看一個web容器是怎樣的啟動過程、也認識下ServletContextListener和ContextLoaderListener
閱讀目錄
- 一、java web容器的啟動
- 二、認識ServletContextListener
- 三、認識ContextLoaderListener
- 四、Spring IOC容器的啟動
一、java web容器的啟動
我們先來看看java web容器是怎樣的啟動過程
1. 啟動一個web項目的時候,web容器會去讀取它的配置文件web.xml,讀取<context-param>節點。
<context-param>包含了Web應用程序的servlet上下文初始化參數的聲明
2.容器創建一個ServletContext(servlet上下文),這個web項目都將共享這個上下文。
3.容器將<context-param>轉換為鍵值對,並交給servletContext。因為listener,filer等在初始化時會用到這些上下文信息,所以要先加載。
4.容器創建<listener>中的類實例,創建監聽器。
5.加載filter和servlet。
load- on-startup 元素在web應用啟動的時候指定了servlet被加載的順序,它的值必須是一個整數。
如果它的值是一個負整數或是這個元素不存在,那么容器會在該servlet被調用的時候,加載這個servlet。如果值是正整數或零,容器在配置的時候就加載並初始化這個servlet,容器必須保證值小的先被加載。如果值相等,容器可以自動選擇先加載誰。
web.xml 的加載順序是:context-param -> listener -> filter -> servlet。
Spring工程中web.xml的配置
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
其中<listener>標簽中定義了spring容器加載器;
二、認識ServletContextListener
上圖中是源碼中接口ServletContextListener的定義,它定義了處理ServletContextEvent 事件的兩個方法contextInitialized()和contextDestroyed()。理.
ServletContextListener能夠監聽ServletContext對象的生命周期,實際上就是監聽Web應用的生命周期。當Servlet容器啟動或終止Web應用時,會觸發ServletContextEvent事件,該事件由ServletContextListener來處理。
三、認識ContextLoaderListener
上圖中是類ContextLoaderListener的定義,它實現了上面的ServletContextListener。
ContextLoaderListener類用來創建Spring application context,並且將application context注冊到servletContext里面去。
四、Spring IOC容器的啟動
結合上面介紹的web容器的啟動過程,以及接口ServletContextListener,ContextLoaderListener,我們來看看Spring IOC容器是怎樣啟動的。
我們知道在web.xml中都會有這個ContextLoaderListener監聽器的配置,我們從上面的ContextLoaderListener接口的定義中可以看到,ContextLoaderListener它實現了ServletContextListener,這個接口里面的方法會結合web容器的生命周期被調用。因為ServletContextListener是ServletContext的監聽者,如果ServletContext發生變化,會觸發相應的事件,而監聽者一直對這些事件進行監聽,如果接受到了監聽的事件,就會作於相應處理。例如在服務器啟動,ServletContext被創建的時候,ContextLoaderListener的contextInitialized()方法被調用,這個方法就是spring容器啟動的入口點。
我們從代碼的角度,具體來看看:
/** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } /** * Close the root web application context. */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); }
由於在ContextLoaderListener中關聯了contextLoader對象,ContextLoader顧名思義就是context的加載器,由它來完成context的加載:
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!"); } 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 { // 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; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } }
初始化web的context做了兩件事情:
1.查看是否指定了父容器,如果存在父容器則獲取父容器;
2.創建webApplicationContext,配置並且刷新實例化整個SpringApplicationContext中的Bean,並指定父容器。
因此,如果我們的Bean配置出錯的話,在容器啟動的時候,會拋異常出來的。
下面看看是怎樣創建webApplicationContext的
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//取得配置的Web應用程序上下文類,如果沒有配置,則使用缺省的類XmlWebApplicationContext Class<?> contextClass = determineContextClass(sc);
//如果contextClass不是可配置的Web應用程序上下文的子類,則拋出異常,停止初始化 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); }
//實例化需要產生的IOC容器 也就是實例化XmlWebApplicationContext return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
實例化好XmlWebApplicationContext后,我們再來看看是如何配置ApplicationContext,並實例化bean的
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); wac.refresh(); }
這個refresh方法是我們后面講IOC容器的重點,這里IOC容器啟動也就到這里了。后面是真正的IOC怎么加載Resource、解析BeanDefinition、注冊、依賴注入。