您好,登錄后才能下訂單哦!
首先我們來看一段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> </web-app>
有經驗的都知道上面一段配置是關于web集成Spring的,每個初次學習Spring的,心中都會疑問為何如此配置,下面跟著我來理解為何如此配置。
首先理解一下什么是listener?
Listener 的作用非常類似于load-on-startup Servlet。用于在Web 應用啟動時,啟動某些后臺程序,這些后臺程序負責為系統運行提供支持。
Listener 與load-on-startup Servlet 的區別在于: Listener 的啟動時機比load-on-startup Servlet 早,只是Listener 是Servlet 2.3 規范之后才出現的。
使用Listener 只需要兩個步驟:
(1)創建Listener 實現類。
(2)在web.xml 文件中配置Listener。
創建Listener 類必須實現ServletContextListener 接口,該接口包含兩個方法。
contextInitialized(ServletContextEvent sce): 啟動Web 應用時,系統調用該Filter的方法。
contextDestroyed(ServletContextEvent sce): 關閉Web 應用時候,系統調用Filter的方法。
先大致介紹一下ContextLoaderListener啟動運行過程:
簡單介紹一下上圖的運行流程:
①啟動項目時觸發contextInitialized方法,該方法就做一件事:通過父類contextLoader的initWebApplicationContext方法創建Spring上下文對象。
②initWebApplicationContext方法做了三件事:創建WebApplicationContext;加載對應的Spring文件創建里面的Bean實例;將WebApplicationContext放入ServletContext(就是Java Web的全局變量)中。
③createWebApplicationContext創建上下文對象,支持用戶自定義的上下文對象,但必須繼承自ConfigurableWebApplicationContext,而Spring MVC默認使用ConfigurableWebApplicationContext作為ApplicationContext(它僅僅是一個接口)的實現。
④configureAndRefreshWebApplicationContext方法用于封裝ApplicationContext數據并且初始化所有相關Bean對象。它會從web.xml中讀取名為contextConfigLocation的配置,這就是spring xml數據源設置,然后放到ApplicationContext中,最后調用傳說中的refresh方法執行所有Java對象的創建。
⑤完成ApplicationContext創建之后就是將其放入ServletContext中,注意它存儲的key值常量。
下面來看一下ContextLoaderListener的源碼:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{ public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } public void contextInitialized(ServletContextEvent event) { //這個方法已經廢棄,返回值為null contextLoader = createContextLoader(); if(contextLoader == null) //因為ContextLoaderListerner繼承了ContextLoader,所以可把自身賦值給 contextLoader = this; //接著就實例化webApplicationContext,由父類ContextLoader實現 contextLoader.initWebApplicationContext(event.getServletContext()); } protected ContextLoader createContextLoader() { return null; } public ContextLoader getContextLoader() { return contextLoader; } public void contextDestroyed(ServletContextEvent event) { if(contextLoader != null) contextLoader.closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } private ContextLoader contextLoader; }
因為它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啟動容器時,就會默認執行它實現的方法。在ContextLoaderListener中關聯了ContextLoader這個類,所以整個加載配置過程由ContextLoader來完成,ContextLoader是一個工具類,用來初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我們繼續追蹤initWebApplicationContext這個方法,我們發現,原來ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默認實現類)放在了ServletContext中,ServletContext也是一個“容器”,也是一個類似Map的結構,而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我們如果要使用WebApplicationContext則需要從ServletContext取出,Spring提供了一個WebApplicationContextUtils類,可以方便的取出WebApplicationContext,只要把ServletContext傳入就可以了。
在創建ContextLoader,會執行它的一段靜態代碼塊。
static { try { // 這里創建一個ClassPathResource對象,載入ContextLoader.properties,用于創建對應的ApplicationContext容器 // 這個文件跟ContextLoader類在同一個目錄下,文件內容如: // org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext // 如此說來,spring默認初始化的是XmlWebApplicationContext ClassPathResource resource = new ClassPathResource("ContextLoader.properties", org/springframework/web/context/ContextLoader); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch(IOException ex) { throw new IllegalStateException((new StringBuilder("Could not load 'ContextLoader.properties': ")).append(ex.getMessage()).toString()); } }
下面我們主要看看ContextLoader這個類的initWebApplicationContext方法。
//以下這段代碼主要判斷是否重復實例化的問題,因為實例化webApplicationContext后,會把它放到servletContext的一個屬性里,所以我們可以從servletContext的屬性取出webApplicationContext,如果不為空,則已經實例化,接著就會拋出異常. 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!");
這個地點是為什么Spring需要引入common-logging這個jar,不然會報錯。
logger = LogFactory.getLog(org/springframework/web/context/ContextLoader);
調用LogFactory.getLog()時發生的事情:
(1).common-logging首先在CLASSPATH中查找commons-logging.properties文件。這個屬性文件至少定義org.apache.commons.logging.Log屬性,它的值應該是上述任意Log接口實現的完整限定名稱。如果找到 org.apache.commons.logging.Log屬相,則使用該屬相對應的日志組件。結束發現過程。
(2).如果上面的步驟失敗(文件不存在或屬相不存在),common-logging接著檢查系統屬性org.apache.commons.logging.Log。如果找到org.apache.commons.logging.Log系統屬性,則使用該系統屬性對應的日志組件。結束發現過程。
(3).如果找不到org.apache.commons.logging.Log系統屬性,common-logging接著在CLASSPATH中尋找log4j的類。如果找到了就假定應用要使用的是log4j。不過這時log4j本身的屬性仍要通過log4j.properties文件正確配置。結束發現過程。
(4).如果上述查找均不能找到適當的Logging API,但應用程序正運行在JRE 1.4或更高版本上,則默認使用JRE 1.4的日志記錄功能。結束發現過程。
(5).最后,如果上述操作都失敗(JRE 版本也低于1.4),則應用將使用內建的SimpleLog。SimpleLog把所有日志信息直接輸出到System.err。結束發現過程。
為了簡化配置 commons-logging ,一般不使用 commons-logging 的配置文件,也不設置與 commons-logging 相關的系統環境變量,而只需將 Log4j 的 Jar 包放置到 classpash 中就可以了。這樣就很簡單地完成了 commons-logging 與 Log4j 的融合。
接著我們可看到:
//接著就會創建webApplicationContext if(context == null) context = createWebApplicationContext(servletContext);
跟進createWebApplicationContext這個方法:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { //獲取相應的class,其實就是ConfigurableWebApplicationContext.class Class contextClass = determineContextClass(sc); //判斷是否為 ConfigurableWebApplicationContext.class或從它繼承 if(!org/springframework/web/context/ConfigurableWebApplicationContext.isAssignableFrom(contextClass)) { //若非ConfigurableWebApplicationContext.class或非其子類則拋出異常 throw new ApplicationContextException((new StringBuilder("Custom context class [")).append(contextClass.getName()).append("] is not of type [").append(org/springframework/web/context/ConfigurableWebApplicationContext.getName()).append("]").toString()); } else { //創建一個contextClass的實例對象返回,然后就是把context強制轉換為configrableWebApplicationContext,實例化spring容器 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); return wac; } }
該方法首先判斷從web.xml文件的初始化參數CONTEXT_CLASS_PARAM(的定義為public static final String CONTEXT_CLASS_PARAM = "contextClass";)獲取的的類名是否存在,如果存在,則容器的Class;否則返回默認的Class。如何獲取默認的容器Class,注意看創建contextLoader時的代碼注釋就知道了, 由此看來,spring不僅有默認的applicationContext的容器類,還允許我們自定義applicationContext容器類,不過Spring不建義我們自定義applicationContext容器類。
protected Class determineContextClass(ServletContext servletContext) { String contextClassName; //從servletContext讀取contextClassName contextClassName = servletContext.getInitParameter("contextClass"); if(contextClassName == null) //否則創建默認的容器的Class對象,即:org.springframework.web.context.support.XmlWebApplicationContext contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); //如果從servletContext讀取到的contextClassName不為空,就返回對應的class類 return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); ClassNotFoundException ex; ex; //如果找不到該類名的類就拋出異常 throw new ApplicationContextException((new StringBuilder("Failed to load custom context class [")).append(contextClassName).append("]").toString(), ex); contextClassName = defaultStrategies.getProperty(org/springframework/web/context/WebApplicationContext.getName()); return ClassUtils.forName(contextClassName, org/springframework/web/context/ContextLoader.getClassLoader()); ex; throw new ApplicationContextException((new StringBuilder("Failed to load default context class [")).append(contextClassName).append("]").toString(), ex); }
接著看核心方法configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if(ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter("contextId"); if(idParam != null) wac.setId(idParam); else if(sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) wac.setId((new StringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getServletContextName())).toString()); else wac.setId((new StringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getContextPath())).toString()); } ApplicationContext parent = loadParentContext(sc); wac.setParent(parent); //把servletContext放到webApplicationContext中,以后可以直接取出來用 wac.setServletContext(sc); String initParameter = sc.getInitParameter("contextConfigLocation"); if(initParameter != null) wac.setConfigLocation(initParameter); //用戶自己的一些設置 customizeContext(sc, wac); //進行bean加載 wac.refresh(); }
我們進入webApplicationContext的refresh方法看一下
public void refresh() throws BeansException, IllegalStateException { synchronized(startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); registerListeners(); finishBeanFactoryInitialization(beanFactory); finishRefresh(); } catch(BeansException ex) { destroyBeans(); cancelRefresh(ex); throw ex; } } }
這是一個同步的方法,這里最核心的方法就是obtainFreshBeanFactory方法。其他的方法都是對webApplicationContext和beanFactory做一些前后的裝飾和準備。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if(logger.isDebugEnabled()) logger.debug((new StringBuilder("Bean factory for ")).append(getDisplayName()).append(": ").append(beanFactory).toString()); return beanFactory; }
這里的核心方法就是refreshBeanFactory();它負責生成BeanFactory并加載bean
protected final void refreshBeanFactory() throws BeansException { //判斷是否已經存在beanFactory如果有則銷毀。 if(hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory);//用戶自己做一些設置 loadBeanDefinitions(beanFactory);//這個方法很關鍵,負責加載所有的bean synchronized(beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch(IOException ex) { throw new ApplicationContextException((new StringBuilder("I/O error parsing bean definition source for ")).append(getDisplayName()).toString(), ex); } }
然后我們進入loadBeanDefinitions(beanFactory);看一下
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setEnvironment(getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
這里主要工作就是new一個XmlBeanDefinitionReader,給它設置environment,resourceLoader和entityResolver,注意一下由于webApplicationContext實現了ResouceLoader接口,所以它本身就是一個ResourceLoader.我們可以看到它并不自己去實現lobeanDefinitions方法,而是委托給XmlBeanDefinitionReader去實現。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { String configLocations[] = getConfigLocations(); if(configLocations != null) { String as[]; int j = (as = configLocations).length; for(int i = 0; i < j; i++) { String configLocation = as[i]; reader.loadBeanDefinitions(configLocation); } } }
這里就對configLocations進行bean的加載,調用重載的方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。