您好,登錄后才能下訂單哦!
首先我們來(lái)看一段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>
有經(jīng)驗(yàn)的都知道上面一段配置是關(guān)于web集成Spring的,每個(gè)初次學(xué)習(xí)Spring的,心中都會(huì)疑問(wèn)為何如此配置,下面跟著我來(lái)理解為何如此配置。
首先理解一下什么是listener?
Listener 的作用非常類似于load-on-startup Servlet。用于在Web 應(yīng)用啟動(dòng)時(shí),啟動(dòng)某些后臺(tái)程序,這些后臺(tái)程序負(fù)責(zé)為系統(tǒng)運(yùn)行提供支持。
Listener 與load-on-startup Servlet 的區(qū)別在于: Listener 的啟動(dòng)時(shí)機(jī)比load-on-startup Servlet 早,只是Listener 是Servlet 2.3 規(guī)范之后才出現(xiàn)的。
使用Listener 只需要兩個(gè)步驟:
(1)創(chuàng)建Listener 實(shí)現(xiàn)類。
(2)在web.xml 文件中配置Listener。
創(chuàng)建Listener 類必須實(shí)現(xiàn)ServletContextListener 接口,該接口包含兩個(gè)方法。
contextInitialized(ServletContextEvent sce): 啟動(dòng)Web 應(yīng)用時(shí),系統(tǒng)調(diào)用該Filter的方法。
contextDestroyed(ServletContextEvent sce): 關(guān)閉Web 應(yīng)用時(shí)候,系統(tǒng)調(diào)用Filter的方法。
先大致介紹一下ContextLoaderListener啟動(dòng)運(yùn)行過(guò)程:
簡(jiǎn)單介紹一下上圖的運(yùn)行流程:
①啟動(dòng)項(xiàng)目時(shí)觸發(fā)contextInitialized方法,該方法就做一件事:通過(guò)父類contextLoader的initWebApplicationContext方法創(chuàng)建Spring上下文對(duì)象。
②initWebApplicationContext方法做了三件事:創(chuàng)建WebApplicationContext;加載對(duì)應(yīng)的Spring文件創(chuàng)建里面的Bean實(shí)例;將WebApplicationContext放入ServletContext(就是Java Web的全局變量)中。
③createWebApplicationContext創(chuàng)建上下文對(duì)象,支持用戶自定義的上下文對(duì)象,但必須繼承自ConfigurableWebApplicationContext,而Spring MVC默認(rèn)使用ConfigurableWebApplicationContext作為ApplicationContext(它僅僅是一個(gè)接口)的實(shí)現(xiàn)。
④configureAndRefreshWebApplicationContext方法用于封裝ApplicationContext數(shù)據(jù)并且初始化所有相關(guān)Bean對(duì)象。它會(huì)從web.xml中讀取名為contextConfigLocation的配置,這就是spring xml數(shù)據(jù)源設(shè)置,然后放到ApplicationContext中,最后調(diào)用傳說(shuō)中的refresh方法執(zhí)行所有Java對(duì)象的創(chuàng)建。
⑤完成ApplicationContext創(chuàng)建之后就是將其放入ServletContext中,注意它存儲(chǔ)的key值常量。
下面來(lái)看一下ContextLoaderListener的源碼:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{ public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } public void contextInitialized(ServletContextEvent event) { //這個(gè)方法已經(jīng)廢棄,返回值為null contextLoader = createContextLoader(); if(contextLoader == null) //因?yàn)镃ontextLoaderListerner繼承了ContextLoader,所以可把自身賦值給 contextLoader = this; //接著就實(shí)例化webApplicationContext,由父類ContextLoader實(shí)現(xiàn) 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; }
因?yàn)樗鼘?shí)現(xiàn)了ServletContextListener這個(gè)接口,在web.xml配置這個(gè)監(jiān)聽(tīng)器,啟動(dòng)容器時(shí),就會(huì)默認(rèn)執(zhí)行它實(shí)現(xiàn)的方法。在ContextLoaderListener中關(guān)聯(lián)了ContextLoader這個(gè)類,所以整個(gè)加載配置過(guò)程由ContextLoader來(lái)完成,ContextLoader是一個(gè)工具類,用來(lái)初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我們繼續(xù)追蹤initWebApplicationContext這個(gè)方法,我們發(fā)現(xiàn),原來(lái)ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默認(rèn)實(shí)現(xiàn)類)放在了ServletContext中,ServletContext也是一個(gè)“容器”,也是一個(gè)類似Map的結(jié)構(gòu),而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我們?nèi)绻褂肳ebApplicationContext則需要從ServletContext取出,Spring提供了一個(gè)WebApplicationContextUtils類,可以方便的取出WebApplicationContext,只要把ServletContext傳入就可以了。
在創(chuàng)建ContextLoader,會(huì)執(zhí)行它的一段靜態(tài)代碼塊。
static { try { // 這里創(chuàng)建一個(gè)ClassPathResource對(duì)象,載入ContextLoader.properties,用于創(chuàng)建對(duì)應(yīng)的ApplicationContext容器 // 這個(gè)文件跟ContextLoader類在同一個(gè)目錄下,文件內(nèi)容如: // org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext // 如此說(shuō)來(lái),spring默認(rèn)初始化的是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這個(gè)類的initWebApplicationContext方法。
//以下這段代碼主要判斷是否重復(fù)實(shí)例化的問(wèn)題,因?yàn)閷?shí)例化webApplicationContext后,會(huì)把它放到servletContext的一個(gè)屬性里,所以我們可以從servletContext的屬性取出webApplicationContext,如果不為空,則已經(jīng)實(shí)例化,接著就會(huì)拋出異常. 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!");
這個(gè)地點(diǎn)是為什么Spring需要引入common-logging這個(gè)jar,不然會(huì)報(bào)錯(cuò)。
logger = LogFactory.getLog(org/springframework/web/context/ContextLoader);
調(diào)用LogFactory.getLog()時(shí)發(fā)生的事情:
(1).common-logging首先在CLASSPATH中查找commons-logging.properties文件。這個(gè)屬性文件至少定義org.apache.commons.logging.Log屬性,它的值應(yīng)該是上述任意Log接口實(shí)現(xiàn)的完整限定名稱。如果找到 org.apache.commons.logging.Log屬相,則使用該屬相對(duì)應(yīng)的日志組件。結(jié)束發(fā)現(xiàn)過(guò)程。
(2).如果上面的步驟失?。ㄎ募淮嬖诨?qū)傧嗖淮嬖冢?,common-logging接著檢查系統(tǒng)屬性org.apache.commons.logging.Log。如果找到org.apache.commons.logging.Log系統(tǒng)屬性,則使用該系統(tǒng)屬性對(duì)應(yīng)的日志組件。結(jié)束發(fā)現(xiàn)過(guò)程。
(3).如果找不到org.apache.commons.logging.Log系統(tǒng)屬性,common-logging接著在CLASSPATH中尋找log4j的類。如果找到了就假定應(yīng)用要使用的是log4j。不過(guò)這時(shí)log4j本身的屬性仍要通過(guò)log4j.properties文件正確配置。結(jié)束發(fā)現(xiàn)過(guò)程。
(4).如果上述查找均不能找到適當(dāng)?shù)腖ogging API,但應(yīng)用程序正運(yùn)行在JRE 1.4或更高版本上,則默認(rèn)使用JRE 1.4的日志記錄功能。結(jié)束發(fā)現(xiàn)過(guò)程。
(5).最后,如果上述操作都失?。↗RE 版本也低于1.4),則應(yīng)用將使用內(nèi)建的SimpleLog。SimpleLog把所有日志信息直接輸出到System.err。結(jié)束發(fā)現(xiàn)過(guò)程。
為了簡(jiǎn)化配置 commons-logging ,一般不使用 commons-logging 的配置文件,也不設(shè)置與 commons-logging 相關(guān)的系統(tǒng)環(huán)境變量,而只需將 Log4j 的 Jar 包放置到 classpash 中就可以了。這樣就很簡(jiǎn)單地完成了 commons-logging 與 Log4j 的融合。
接著我們可看到:
//接著就會(huì)創(chuàng)建webApplicationContext if(context == null) context = createWebApplicationContext(servletContext);
跟進(jìn)createWebApplicationContext這個(gè)方法:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { //獲取相應(yīng)的class,其實(shí)就是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 { //創(chuàng)建一個(gè)contextClass的實(shí)例對(duì)象返回,然后就是把context強(qiáng)制轉(zhuǎn)換為configrableWebApplicationContext,實(shí)例化spring容器 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); return wac; } }
該方法首先判斷從web.xml文件的初始化參數(shù)CONTEXT_CLASS_PARAM(的定義為public static final String CONTEXT_CLASS_PARAM = "contextClass";)獲取的的類名是否存在,如果存在,則容器的Class;否則返回默認(rèn)的Class。如何獲取默認(rèn)的容器Class,注意看創(chuàng)建contextLoader時(shí)的代碼注釋就知道了, 由此看來(lái),spring不僅有默認(rèn)的applicationContext的容器類,還允許我們自定義applicationContext容器類,不過(guò)Spring不建義我們自定義applicationContext容器類。
protected Class determineContextClass(ServletContext servletContext) { String contextClassName; //從servletContext讀取contextClassName contextClassName = servletContext.getInitParameter("contextClass"); if(contextClassName == null) //否則創(chuàng)建默認(rèn)的容器的Class對(duì)象,即:org.springframework.web.context.support.XmlWebApplicationContext contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); //如果從servletContext讀取到的contextClassName不為空,就返回對(duì)應(yīng)的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中,以后可以直接取出來(lái)用 wac.setServletContext(sc); String initParameter = sc.getInitParameter("contextConfigLocation"); if(initParameter != null) wac.setConfigLocation(initParameter); //用戶自己的一些設(shè)置 customizeContext(sc, wac); //進(jìn)行bean加載 wac.refresh(); }
我們進(jìn)入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; } } }
這是一個(gè)同步的方法,這里最核心的方法就是obtainFreshBeanFactory方法。其他的方法都是對(duì)webApplicationContext和beanFactory做一些前后的裝飾和準(zhǔn)備。
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();它負(fù)責(zé)生成BeanFactory并加載bean
protected final void refreshBeanFactory() throws BeansException { //判斷是否已經(jīng)存在beanFactory如果有則銷毀。 if(hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory);//用戶自己做一些設(shè)置 loadBeanDefinitions(beanFactory);//這個(gè)方法很關(guān)鍵,負(fù)責(zé)加載所有的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); } }
然后我們進(jìn)入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一個(gè)XmlBeanDefinitionReader,給它設(shè)置environment,resourceLoader和entityResolver,注意一下由于webApplicationContext實(shí)現(xiàn)了ResouceLoader接口,所以它本身就是一個(gè)ResourceLoader.我們可以看到它并不自己去實(shí)現(xiàn)lobeanDefinitions方法,而是委托給XmlBeanDefinitionReader去實(shí)現(xiàn)。
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); } } }
這里就對(duì)configLocations進(jìn)行bean的加載,調(diào)用重載的方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。