1. 程式人生 > >spring容器啟動的三種方式

spring容器啟動的三種方式

一、在Web專案中,啟動Spring容器的方式有三種,ContextLoaderListener、ContextLoadServlet、ContextLoaderPlugin。

1.1、監聽器方式:

web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/classes/applicationContext-*.xml</param-value>
</context-param>
<listener> 
    <listener-class>org.springframework.web.context.ContextLoaderListener
 </listener-class>
 </listener>

還可以通過<import resource="classpath:/spring/spring-xxx.xml"/>的方式把其他的配置抱進來。

1.2、servlet方式:

<servlet> 
    <servlet-name>context</servlet-name> 
    <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet> 

這種方式,spring3.0以後不再支援,建議使用監聽器方式。你可以檢視一下spring3.0的change log http://static.springsource.org/spring/docs/3.0.x/changelog.txt  裡面註明:  removed ContextLoaderServlet and Log4jConfigServlet 

1.3、通過plugin配置方式:

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">  
    <set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml" />  
</plug-in> 

該方式適用於,spring與struts等整合,在Struts的配置檔案struts-config.xml裡面配置一個ContextLoaderPlugIn,用於spring的初始化工作。

1.4、補充兩點:

1、如果要spring-mvc的話,需要在web.xml檔案中配置如下:

<servlet>
    <servlet-name>simpleSpringMVC/servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatchServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>simpleSpringMVC</servlet-name>
    <url-pattern>*.htm</url-pattern>
</servlet-mapping>

2、如果要使用springSecurity的話,首先需要在web.xml進行以下配置,

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>  <!-- 預設是false -->
    </init-param>
 </filter>

<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

二、監聽器啟動方式也分為3種情況

2.1、IntrospectorCleanupListener簡介

  org.springframework.web.util.IntrospectorCleanupListener監聽器主要負責處理由JavaBean Introspector使用而引起的緩衝洩露, 它是一個在web應用關閉時清除JavaBean Introspector的監聽器,在web.xml中註冊這個listener可以保證在web應用關閉的時候釋放掉與這個web應用相關的class loader和由它管理的類。

org.springframework.web.util.IntrospectorCleanupListener原始碼中對其的解釋如下:

        Listener that flushes the JDK's JavaBeans Introspector cache on web app shutdown. Register this listener in your web.xml to guarantee proper release of the web application class loader and its loaded classes.

        在Web應用程式關閉時IntrospectorCleanupListener將會重新整理JDK的JavaBeans的Introspector快取。在你的web.xml中註冊這個listener來確保Web應用程式的類載入器以及其載入的類正確的釋放資源。

        If the JavaBeans Introspector has been used to analyze application classes, the system-level Introspector cache will hold a hard reference to those classes. Consequently, those classes and the web application class loader will not be garbage-collected on web app shutdown! This listener performs proper cleanup, to allow for garbage collection to take effect.      

       如果JavaBeans的Introspector已被用來分析應用程式類,系統級的Introspector快取將持有這些類的一個硬引用。因此,這些類和Web應用程式的類載入器在Web應用程式關閉時將不會被垃圾收集器回收!而IntrospectorCleanupListener則會對其進行適當的清理,已使其能夠被垃圾收集器回收。

       Unfortunately, the only way to clean up the Introspector is to flush the entire cache, as there is no way to specifically determine the application's classes referenced there. This will remove cached introspection results for all other applications in the server too.

不幸的是,唯一能夠清理Introspector的方法是重新整理整個Introspector快取,沒有其他辦法來確切指定應用程式所引用的類。這將刪除所有其他應用程式在伺服器的快取的Introspector結果。

       Note that this listener is not necessary when using Spring's beans infrastructure within the application, as Spring's own introspection results cache will immediately flush an analyzed class from the JavaBeans Introspector cache and only hold a cache within the application's own ClassLoader. Although Spring itself does not create JDK Introspector leaks, note that this listener should nevertheless be used in scenarios where the Spring framework classes themselves reside in a 'common' ClassLoader (such as the system ClassLoader). In such a scenario, this listener will properly clean up Spring's introspection cache.

       請注意,在使用Spring內部的bean機制時,不需要使用此監聽器,因為Spring自己的introspection results cache將會立即重新整理被分析過的JavaBeans Introspector cache,而僅僅會在應用程式自己的ClassLoader裡面持有一個cache。雖然Spring本身不產生洩漏,注意,即使在Spring框架的類本身駐留在一個“共同”類載入器(如系統的ClassLoader)的情況下,也仍然應該使用使用IntrospectorCleanupListener。在這種情況下,這個IntrospectorCleanupListener將會妥善清理Spring的introspection cache。

       Application classes hardly ever need to use the JavaBeans Introspector directly, so are normally not the cause of Introspector resource leaks. Rather, many libraries and frameworks do not clean up the Introspector: e.g. Struts and Quartz.

       應用程式類,幾乎不需要直接使用JavaBeans Introspector,所以,通常都不是Introspector resource造成記憶體洩露。相反,許多庫和框架,不清理Introspector,例如: Struts和Quartz。

       Note that a single such Introspector leak will cause the entire web app class loader to not get garbage collected! This has the consequence that you will see all the application's static class resources (like singletons) around after web app shutdown, which is not the fault of those classes!

        需要注意的是一個簡單Introspector洩漏將會導致整個Web應用程式的類載入器不會被回收!這樣做的結果,將會是在web應用程式關閉時,該應用程式所有的靜態類資源(比如:單例項物件)都沒有得到釋放。而導致記憶體洩露的根本原因其實並不是這些未被回收的類!

       This listener should be registered as the first one in web.xml, before any application listeners such as Spring's ContextLoaderListener. This allows the listener to take full effect at the right time of the lifecycle. 

       IntrospectorCleanupListener應該註冊為web.xml中的第一個Listener,在任何其他Listener之前註冊,比如在Spring's ContextLoaderListener註冊之前,才能確保IntrospectorCleanupListener在Web應用的生命週期適當時機生效。

Java程式碼 

package  org.springframework.web.util;   
  
import  java.beans.Introspector;   
import  javax.servlet.ServletContextEvent;   
import  javax.servlet.ServletContextListener;   
  
public   class  IntrospectorCleanupListener  implements  ServletContextListener   {   
     public   void  contextInitialized(ServletContextEvent event)   {   
    }    
    
      public   void  contextDestroyed(ServletContextEvent event)   {   
        Introspector.flushCaches();   
    }    
}   

用法如下  用法很簡單,就是在web.xml中加入:    

<listener>    
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>    
</listener> 

“Introspector.flushCaches();”就可以把快取中的內容清楚掉了。

相關用法:

1、在web.xml中註冊IntrospectorCleanupListener監聽器以解決struts等框架可能產生的記憶體洩露問題

增加方式如下:

<listener>  
        <listener-class>  
            org.springframework.web.util.IntrospectorCleanupListener  
        </listener-class>  
</listener>

2、使用IntrospectorCleanupListener 解決quartz引起的記憶體洩漏問題

"在伺服器執行過程中,Spring不停的執行的計劃任務和OpenSessionInViewFilter,使得Tomcat反覆載入物件而產生框架並用時可能產生的記憶體洩漏,則使用IntrospectorCleanupListener作為相應的解決辦法。"

        只知道servlet標準不允許在web容器內自行做執行緒管理,quartz的問題確實存在。  

對於Web容器來說,最忌諱應用程式私自啟動執行緒,自行進行執行緒排程,像Quartz這種在web容器內部預設就自己啟動了10執行緒進行非同步job排程的框架本身就是很危險的事情,很容易造成servlet執行緒資源回收不掉,所以我一向排斥使用quartz。

quartz還有一個問題就是不支援cluster。導致使用quartz的應用都沒有辦法做群集。

如果是我的話,我採取的辦法就是自己單獨啟動一個Job Server,來跑job,不會部署在web容器中。

2.2、ContextLoaderListener簡介

類的繼承關係:

  與BeanFactory通常以程式設計的方式被建立不同的是,ApplicationContext能以宣告的方式建立,如使用ContextLoader。當然你也可以使用ApplicationContext的實現之一來以程式設計的方式建立ApplicationContext例項。首先,讓我們先分析ContextLoader介面及其實現。

    ContextLoader介面有兩個實現:ContextLoaderListener和ContextLoaderServlet。兩者都實現同樣的功能,但不同的是,ContextLoaderListener不能在與Servlet 2.2相容的web容器中使用。根據Servlet 2.4規範, servlet context listener要在web應用程式的servlet context建立後立即執行,並要能夠響應第一個請求(在servlet context要關閉時也一樣):這樣一個servlet context listener是初始化Spring ApplicationContext的理想場所。雖然使用哪個完全取決於你,但是在同等條件下應該首選ContextLoaderListener;對於更多相容性的資訊,請檢視ContextLoaderServlet的JavaDoc。

    你可以象下面那樣使用ContextLoaderListener來註冊一個ApplicationContext:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- or use the ContextLoaderServlet instead of the above listener
<servlet>
  <servlet-name>context</servlet-name>
  <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
-->

    啟動步驟:

  1、監聽器首先檢查contextConfigLocation引數,如果它不存在,它將使用/WEB-INF/applicationContext.xml作為預設值。如果已存在,它將使用分隔符(逗號、冒號或空格)將字串分解成應用上下文將位置路徑。ContextLoaderServlet同ContextLoaderListener一樣使用'contextConfigLocation'引數。

2.3、Log4jConfigListener簡介

使用spring中的Log4jConfigListener有如如下好處:    1. 動態的改變記錄級別和策略,即修改log4j.properties,不需要重啟web應用,這需要在web.xml中設定一下,如《Effective Enterprise Java》所說。    2. 把log檔案定在 /WEB-INF/logs/ 而不需要寫絕對路徑。   因為 系統把web目錄的路徑壓入一個叫webapp.root的系統變數。這樣寫log檔案路徑時不用寫絕對路徑了。log4j.appender.logfile.File=${webapp.root}/WEB-INF/logs/myfuse.log    3. 可以把log4j.properties和其他properties一起放在/WEB-INF/ ,而不是Class-Path。    4.log4jRefreshInterval為60000表示 開一條watchdog執行緒每60秒掃描一下配置檔案的變化;   

示例:在web.xml 新增

   <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>WEB-INF/log4j.properties</param-value>
    </context-param>

    <context-param>
        <param-name>log4jRefreshInterval</param-name>
        <param-value>60000</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

其中第二部分是<log4jRefreshInterval>節點是能夠動態修改log4j.properties的關鍵,容器會每60秒掃描log4j的配置檔案。有一點就是我們如果用RollingFileAppender或者是FileAppender時,可以通過${webapp.root}來定位到伺服器的釋出的該專案下,這是spring把web目錄的路徑壓入到了webap.root的系統變數。然後,在log4j.properties裡就可以這樣定義logfile位置log4j.appender.logfile.File=${webapp.root}/WEB-INF/logs/myfuse.log 

如果有多個web應用,怕webapp.root變數重複,可以在context-param裡定義webAppRootKey,如下一個示例:

在使用spring先後開發了兩個模組,單獨測試都正常。也先後上線執行,之後發現有個模組在啟動Tomcat後總是初始化失敗,必須到tomcat管理控制檯手動啟動。找了半天也沒發現原因。後來管理員在每次重啟Tomcat後這個模組沒有執行導致一堆問題和麻煩,今天特意查看了其他的tomcat日誌檔案,終於發現了問題所在,原來是Log4jConfigListener。使用它是為了隨時調整列印日誌的級別而不用重啟服務。沒想到沒有享受到它的便利,反而出了一堆問題,只能怪自己沒有稍微仔細研究一下。  web.xml

    <context-param>  
        <param-name>webAppRootKey</param-name>  
        <param-value>cang.qing6.com.root</param-value>  
    </context-param>  
  
    <context-param>  
        <param-name>log4jConfigLocation</param-name>  
        <param-value>/WEB-INF/classes/log4j.properties</param-value>  
    </context-param>  
  
    <context-param>  
        <param-name>log4jRefreshInterval</param-name>  
        <param-value>6000</param-value>  
    </context-param>  
    <listener>  
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
    </listener> 

log4j.properties配置

layoutPattern=[%d{HH:mm:ss}] %-5p : %m%n   
log.file=${message.web.root}/logs/app.log   
  
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender   
log4j.appender.logfile.File=${log.file}   
log4j.appender.logfile.Append=true  
log4j.appender.logfile.DatePattern='.'yyyyMMdd   
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout   
log4j.appender.logfile.layout.ConversionPattern=${layoutPattern}

其實需要注意的地方就是應用伺服器下有不止一個的應用在使用spring的Log4jConfigListener需要修改web環境中webAppRootKey值(這個值其實是web應用的根目錄在環境變數名,這樣在log4j配置檔案中如果有相對web目錄的路徑就不用寫死了)。 否則兩個預設值web.root在環境變數中就會有衝突導致第二個應用啟動失敗。