1. 程式人生 > 實用技巧 >ContextLoaderListener自動裝配配置資訊(轉)

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類了