随笔 - 2084  文章 - 4  评论 - 261 

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简介

  关于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在环境变量中就会有冲突导致第二个应用启动失败。

posted on 2015-12-27 23:22  duanxz  阅读(50094)  评论(1编辑  收藏