一、在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簡介
關於java bean的內省見:java的reflection和introspector
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在環境變量中就會有沖突導致第二個應用啟動失敗。