ContextLoaderListener自動裝配配置資訊(轉)
原文地址:https://blog.csdn.net/qq_35029061/article/details/82936895
Spring配置過程中要考慮兩個監聽器:ContextLoaderListener與RequestContextListener。
ContextLoaderListener extends ContextLoader implements ServletContextListener。
ServletContextListener extends EventListener。
ServletContextListener只負責監聽Web容器的啟動和關閉的事件。
ContextLoaderListener(或ContextLoaderServlet)將Web容器與spring容器進行整合。
spring初始化
1 import org.springframework.web.context.ContextLoader; 2 import org.springframework.web.context.ContextLoaderListener 3 import org.springframework.web.context.WebApplicationContext; 4 public class ContextLoaderListener extends ContextLoader implements ServletContextListener
實現spring管理bean
1 import javax.naming.Context; 2 import javax.naming.InitialContext; 3 import javax.sql.DataSource; 4 5 import org.springframework.web.context.ContextLoader; 6 import org.springframework.web.context.WebApplicationContext; 7 8 private DataSource lookDS(String nameStr) { 9 Context env1 = null; 10 try { 11 WebApplicationContext wc = ContextLoader.getCurrentWebApplicationContext(); 12 DataSource dSource = (DataSource)wc.getBean(nameStr); 13 if(dSource != null){ 14 return dSource; 15 } 16 17 env1 = (Context) new InitialContext(); 18 return (DataSource) env1.lookup(nameStr); 19 } catch (Exception e) { 20 try { 21 Context env2 = (Context) env1.lookup("java:comp/env"); 22 return (DataSource) env2.lookup(nameStr); 23 } catch (Exception e2) { 24 String msg = "查詢資料來源:" + nameStr + "失敗:" + 25 CoreException.getStackTraceMsg(e); 26 throw new CoreException(msg); 27 } 28 } 29 }
一、ContextLoaderListener
1.ContextLoaderListener的作用
ContextLoaderListener的作用就是啟動Web容器時,自動裝配ApplicationContext.xml的配置資訊。
ContextLoaderListener繼承自ContextLoader,實現的是ServletContextListener介面。在web.xml配置這個監聽器,啟動容器時,就會預設執行它實現的方法。
ContextLoaderListener可以指定在Web應用程式啟動時載入Ioc容器,正是通過ContextLoader來實現的,ContextLoader來完成實際的WebApplicationContext,也就是Ioc容器的初始化工作。
如果沒有顯式宣告,則系統預設在WEB-INF/applicationContext.xml。
在web.xml中配置
1 <!-- Spring配置檔案開始 --> 2 <context-param> 3 <param-name>contextConfigLocation</param-name> 4 <param-value> 5 classpath:spring-config.xml 6 </param-value> 7 </context-param> 8 <listener> 9 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 10 </listener> 11 <!-- Spring配置檔案結束 -->
在一個團隊使用Spring的實際專案中,應該需要多個Spring的配置檔案,如何使用和交叉引用的問題:
1 <context-param> 2 <param-name>contextConfigLocation</param-name> 3 <param-value>/WEB-INF/spring-base.xml</param-value> 4 </context-param> 5 <servlet> 6 <servlet-name>springmvc</servlet-name> 7 <servlet-class>org.springframework.web.servlet.DispatcherServlet 8 </servlet-class> 9 <init-param> 10 <param-name>contextConfigLocation</param-name> 11 <param-value>/WEB-INF/springmvc.xml</param-value> 12 </init-param> 13 <load-on-startup>1</load-on-startup> 14 </servlet> 15 <servlet-mapping> 16 <servlet-name>springmvc</servlet-name> 17 <url-pattern>/</url-pattern> 18 </servlet-mapping> 19 <listener> 20 <listener-class>org.springframework.web.context.ContextLoaderListener 21 </listener-class> 22 </listener>
2.ServletContextListener介面
ContextLoaderListener類實現了javax.servlet.ServletContextListener介面。ServletContextListener介面能夠監聽ServletContext物件的生命週期,因為每個web應用僅有一個ServletContext物件,故實際上該介面監聽的是整個web應用。
ServletContextListener接口裡的函式會結合Web容器的生命週期被呼叫。因為ServletContextListener是ServletContext的監聽者,如果ServletContext發生變化,會觸發相應的事件,而監聽器一直對事件監聽,如果接收到了變化,就會做出預先設計好的相應動作。由於ServletContext變化而觸發的監聽器的響應具體包括:在伺服器啟動時,ServletContext被建立的時候,伺服器關閉時,ServletContext將被銷燬的時候等。
實現該介面的類在web.xml中作為監聽器配置後,當web應用啟動後,會觸發ServletContextEvent事件,呼叫ContextLoaderListener的contextInitialized(ServletContextEvent sce)方法。
3.繼承ContextLoader的作用
ContextLoaderListener的作用就是啟動Web容器時,讀取在contextConfigLocation中定義的xml檔案,自動裝配ApplicationContext的配置資訊,併產生WebApplicationContext物件,然後將這個物件放置在ServletContext的屬性裡,這樣我們只要得到Servlet就可以得到WebApplicationContext物件,並利用這個物件訪問spring容器管理的bean。
簡單來說,就是上面這段配置為專案提供了spring支援,初始化了Ioc容器。
ContextLoaderListener通過一個ContextLoader物件來初始化Spring容器。在contextInitialized方法中呼叫contextLoader.initWebApplicationContext(event.getServletContext())。
ContextLoader類的initWebApplicationContext方法即可返回一個WebApplicationContext物件context。並通過servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context)將WebApplicationContext物件放置在ServletContext物件中。initWebApplicationContext方法通過呼叫以下方法例項化並設定WebApplicationContext物件。
1 protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) 2 throws BeansException 3 { 4 Class contextClass = determineContextClass(servletContext);//通過servletContext確定WebApplicationContext的具體型別 5 if(!(org.springframework.web.context.ConfigurableWebApplicationContext.class).isAssignableFrom(contextClass)) 6 { 7 throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + (org.springframework.web.context.ConfigurableWebApplicationContext.class).getName() + "]"); 8 } else 9 { 10 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); 11 wac.setParent(parent); 12 wac.setServletContext(servletContext); 13 wac.setConfigLocation(servletContext.getInitParameter("contextConfigLocation"));//設定配置檔案的路徑名 14 customizeContext(servletContext, wac); 15 wac.refresh(); 16 return wac; 17 } 18 }
因此可以通過WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)獲取WebApplicationContext。內部實現是通過servletContext物件查詢該物件
屬性名為WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
4.WebApplicationContext如何完成建立
監聽器一直對事件監聽,如果接收到了變化,就會做出預先設計好的相應動作”。而監聽器的響應動作就是在伺服器啟動時contextInitialized會被呼叫,關閉的時候contextDestroyed被呼叫。
把建立好的springcontext,交給application內建物件,提供給監聽器/過濾器/攔截器使用。
1 @Service 2 public class CommonListener implements ServletContextListener{ 3 4 @Autowired 5 private UserService userService; 6 7 public void contextInitialized(ServletContextEvent servletContextEvent) { 8 //Exception sending context initialized event to listener instance of class com.walidake.listener.CommonListener java.lang.NullPointerException 9 System.out.println(userService.findUser()); 10 } 11 12 public void contextDestroyed(ServletContextEvent servletContextEvent) { 13 // TODO Auto-generated method stub 14 15 } 16 17 }
spring是管理邏輯層和資料訪問層的依賴。而listener是web元件,那麼必然不能放在spring裡面。真正例項化它的應該是tomcat,在啟動載入web.xml例項化的。上層的元件不可能被下層例項化得到。
因此,即使交給Spring例項化,它也沒能力去幫你例項化。真正實現例項化的還是web容器。
然而NullPointerException並不是來自這個原因,“ContextLoader來完成實際的WebApplicationContext,也就是Ioc容器的初始化工作”。我們並沒有繼承ContextLoader,沒有Ioc容器的初始化,是無法實現依賴注入的。
因此,我們想到另一種解決方案,能不能通過new ClassPathXmlApplicationContext的方式,像測試用例那樣取得Ioc容器中的bean物件。
1 ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); 2 userService = context.getBean(UserService.class); 3 System.out.println(userService.findUser());
發現可以正常打印出結果。然而觀察日誌後發現,原本的單例被建立了多次(譬如userServiceImpl等)。因此該方法並不可取。
那麼,由於被建立了多次,是不是可以說明專案中已存在了WebApplicationContext?
是的。“在初始化ContextLoaderListener成功後,spring context會存放在servletContext中”,意味著我們完全可以從servletContext取出WebApplicationContext,然後getBean取得需要的bean物件。
1 ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContextEvent.getServletContext()); 2 userService = context.getBean(UserService.class); 3 datas = userService.findUser(); 4 servletContextEvent.getServletContext().setAttribute("datas", datas);
二、WebApplicationContext
WebApplicationContext是ApplicationContext的子介面,縱觀Spring框架的幾種容器,BeanFactory作為頂級的介面,是所有IOC容器的最上層介面,顧名思義WebApplicationContext是依賴於Web容器的一個Spring的IOC容器。前提條件是web容器啟動後這個容器才能啟動。那麼如何藉助web容器來啟動Spring web的上下文?
第一種方式:我們可以通過org.springframework.web.context.ContextLoaderServlet;
第二種式:org.springframework.web.context.ContextLoaderListener.
這兩種方式有什麼不同呢?listener完全是觀察者的設計,僅僅執行的是監聽的任務,而servlet的啟動要稍微延遲一些,啟動的前後順序是有影像的。所以我認為listener更好用一些,實際開發中的框架配置中也是listener更多一些,這一點大家應該有所體會。
1 <context-param> 2 <param-name>contextConfigLocation</param-name> 3 <param-value>/WEB-INF/applicationContext.xml</param-value> 4 </context-param> 5 <listener> 6 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 7 </listener>
通過上圖可以發現ContextLoaderListener繼承了ContextLoader類以及實現了ServletContextListener介面,ServletContextListener又實現了EvenListener介面,所以這個監聽器具有事件監聽的功能,並且是監聽了web容器,web容器啟動馬上載入執行。ContextLoader感覺更像是執行載入web容器的一個小小的core元件,負責執行載入web容器的邏輯。下面重點來說一說這個。
1 public void contextInitialized(ServletContextEvent event) { 2 //首先先獲取ContextLoader物件 3 this.contextLoader = createContextLoader(); 4 if (this.contextLoader == null) { 5 this.contextLoader = this; 6 } 7 //通過servletContext物件去載入WebApplicationContext 8 this.contextLoader.initWebApplicationContext(event.getServletContext()); 9 }View Code
initWebApplicationContext的主要程式碼:
1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 2 //去servlet容器中去尋找org.springframework.web.context.WebApplicationContext.root作為key的value,也就是webApplicationContext物件 3 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 4 throw new IllegalStateException( 5 "Cannot initialize context because there is already a root application context present - " + 6 "check whether you have multiple ContextLoader* definitions in your web.xml!"); 7 } 8 9 Log logger = LogFactory.getLog(ContextLoader.class); 10 servletContext.log("Initializing Spring root WebApplicationContext"); 11 if (logger.isInfoEnabled()) { 12 logger.info("Root WebApplicationContext: initialization started"); 13 } 14 long startTime = System.currentTimeMillis(); 15 16 try { 17 // Store context in local instance variable, to guarantee that 18 // it is available on ServletContext shutdown. 19 if (this.context == null) { 20 this.context = createWebApplicationContext(servletContext); 21 } 22 if (this.context instanceof ConfigurableWebApplicationContext) { 23 configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext); 24 } 25 //將ApplicationContext放入ServletContext中,其key為<WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 26 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 27 28 //將ApplicationContext放入ContextLoader的全域性靜態常量Map中,其中key為:Thread.currentThread().getContextClassLoader()即當前執行緒類載入器 29 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 30 if (ccl == ContextLoader.class.getClassLoader()) { 31 currentContext = this.context; 32 } 33 else if (ccl != null) { 34 currentContextPerThread.put(ccl, this.context); 35 } 36 37 if (logger.isDebugEnabled()) { 38 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + 39 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); 40 } 41 if (logger.isInfoEnabled()) { 42 long elapsedTime = System.currentTimeMillis() - startTime; 43 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); 44 } 45 46 return this.context; 47 } 48 catch (RuntimeException ex) { 49 logger.error("Context initialization failed", ex); 50 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 51 throw ex; 52 } 53 catch (Error err) { 54 logger.error("Context initialization failed", err); 55 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); 56 throw err; 57 } 58 }View Code
Spring初始化之後,將ApplicationContext存到在了兩個地方(servletContext中和currentContextPerThread中),那麼是不是意味著我們可以通過兩種方式取得ApplicationContext?
第一種獲取方式:
1 request.getSession().getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")
在WebApplicationContextUtils類中有一個靜態方法:
1 public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) 2 throws IllegalStateException { 3 4 WebApplicationContext wac = getWebApplicationContext(sc); 5 if (wac == null) { 6 throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); 7 } 8 return wac; 9 } 10 11 public static WebApplicationContext getWebApplicationContext(ServletContext sc) { 12 return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 13 }View Code
通過執行上面的方法可以獲取WebApplicationContext物件。
第二種方法:
借用ApplicationContextAware,ApplicationContext的幫助類能夠自動裝載ApplicationContext,只要你將某個類實現這個介面,並將這個實現類在Spring配置檔案中進行配置,Spring會自動幫你進行注入 ApplicationContext.ApplicationContextAware的程式碼結構如下:
1 public interface ApplicationContextAware { 2 void setApplicationContext(ApplicationContext applicationContext) throws BeansException; 3 }
就這一個介面。可以這樣簡單的實現一個ApplicationContextHelper類:
1 public class ApplicationHelper implements ApplicationContextAware { 2 3 4 private ApplicationContext applicationContext; 5 public void setApplicationContext(ApplicationContext applicationContext) 6 throws BeansException { 7 this.applicationContext = applicationContext; 8 } 9 public ApplicationContext getApplicationContext(){ 10 return this.applicationContext; 11 } 12 }View Code
通過ApplicationHelper我們就可以獲得咱們想要的AppilcationContext類了