ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,web.xml配置这个监听器启动容器时,就会默认执行它实现的方法。在ContextLoader-Listener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。ContextLoader可以由 ContextLoaderListener和ContextLoaderServlet生成。查看ContextLoaderServlet的API,它也关联了ContextLoader这个类而且它实现了HttpServlet这个接口;ContextLoader创建的是XmlWebApplicationContext这样一个类,实现的接口是WebApplicationContext->ConfigurableWebApplicationContext->ApplicationContext->BeanFactory。这样一来spring中的所有bean都由这个类来创建。如果在web.xml中不写任何参数配置信息,默认的路径是/WEB-INF/applicationContext.xml,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml;如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
contextConfigLocation
/WEB-INF/classes/applicationContext-*.xml
在
contextConfigLocation
/WEB-INF/applicationContext-*.xml,classpath*:applicationContext-*.xml
Spring提供ServletContentListener的一个实现类ContextLoaderListener监听器,该类可以作为Listener使用,在启动Tomcat容器的时候,该类的作用就是自动装载ApplicationContext的配置信息,如果没有设置contextConfigLocation的初始参数则会使用默认参数WEB-INF路径下的application.xml文件。
在分析IntrospectorCleanupListener之前,先了解一下Introspector。Introspector是JDK中java.beans包下的类,它为目标JavaBean提供了一种了解原类方法、属性和事件的标准方法。通俗的说,就是可以通过Introspector构建一个BeanInfo对象,而这个BeanInfo对象中包含了目标类中的属性、方法和事件的描述信息,然后可以使用这个BeanInfo对象对目标对象进行相关操作。下面看一个简单的示例会很容易明白。为了简单,Student类中只有一个name属性。
结果输出:Student{name='张三'}
通过查看Introspector.getBeanInfo方法的源码会发现,Introspector在构建一个BeanInfo对象的时候,会将构建的BeanInfo对象和原类缓存到一个Map中,源码如下。
通过上的代码可以得出,Introspector间接持有了BeanInfo的强引用。如果使用Introspector操作了很多类,那么Introspector将间接持有这些BeanInfo的强引用。在发生垃圾收集的时候,检测到这些BeanInfo存在引用链,则这些类和对应的类加载器将不会被垃圾收集器回收,进而导致内存泄漏。所以,为了解决这个问题,在使用Introspector操作完成后,调用Introspector类的flushCaches方法清除缓存。
通过上面的代码会发现,清除的时候是清空了整个缓存,因为没有很好的办法来确定每个缓存是属于哪个应用的,所以清除的时候会清除所有应用的缓存。
上面分析了Introspector的作用和影响,那IntrospectorCleanupListener和Introspector有什么关系呢?
IntrospectorCleanupListener是spring-web jar中的类,源码如下。
IntrospectorCleanupListener实现了ServletContextListener接口,也就是说,在web容器初始化(准确的说是在filters或servlets初始化之前)的时候会执行contextInitialized方法,在ServletContext销毁(准确的说是在filters和servlets销毁之后)的时候会执行contextDestroyed方法。从图中contextDestroyed方法,可以看到在销毁ServletContext的时候调用了Introspector.flushCaches方法,清空了对应缓存。IntrospectorCleanupListener中为什么要这么做?难道是Spring使用Introspector操作后没有清空对应缓存?查看IntrospectorCleanupListener类的源码,会发现有这样一段标注。
大意是说,在使用Spring本身的时候并不需要使用此监听器,因为Spring自己的内部机制会立即清空对应的缓存。虽然,Spring本身不存在这样的问题,但是如果和其它框架结合使用,而其它框架有这个问题,如Struts、Quartz等,那就需要配置这个监听器,在销毁ServletContext的时候清空对应缓存。有一点需要注意的是,像这样一个简单的Introspector内存泄漏将会导致整个应用的类加载器不会被垃圾收集器回收,如果有内存泄漏的问题,可以考虑此因素。
此监听器主要用于解决java.beans.Introspector导致的内存泄漏的问题。JDK中的java.beans.Introspector类的用途是发现Java类是否符合JavaBean规范。如果有的框架或程序用到了Introspector类,那么就会启用一个系统级别的缓存,此缓存会存放一些曾加载并分析过的JavaBean的引用。当Web服务器关闭时,由于此缓存中存放着这些JavaBean的引用,所以垃圾回收器无法回收Web容器中的JavaBean对象,最后导致内存变大。而org.springframework.web.util.IntrospectorCleanupListener就是专门用来处理Introspector内存泄漏问题的辅助类。IntrospectorCleanupListener会在Web服务器停止时清理Introspector缓存,使那些Javabean能被垃圾回收器正确回收。Spring自身不会出现这种问题,因为Spring在加载并分析完一个类之后会马上刷新。java.beans.-Introspector缓存,这就保证Spring中不会出现这种内存泄漏的问题。但有些程序和框架在使用了JavaBeans Introspector之后,没有进行清理工作(如Quartz,Struts),最后导致内存泄漏。
在以往的工作经历中,多次看到在web.xml中将IntrospectorCleanupListener配置成非第一个listener。官方的表述是必须将此监听器配置成web.xml中的第一个listener,才能在合适的时间发挥最有效的作用。原因其实很简单,在Servlet3.0规范之前,监听器的调用是随机的,而从Servlet3.0开始,监听器的调用顺序是根据其在web.xml中配置的顺序,并且实现ServletContextListener的监听器,contextInitialized方法调用顺序是按照在web.xml中配置的顺序正序依次执行,而contextDestroyed方法的调用顺序是按照在web.xml中配置的顺序逆序依次执行。所以,如果Introspector-CleanupListener被配置成了第一个listener,那么它的contextDestroyed方法将最后一个执行,将发挥最有效的清除作用;而如果不是,那么可能会残留未被清除的缓存。
org.springframework.web.context.request.RequestContextListener
如果你用的是早期版本的web容器(Servlet 2.4以前),那么你要使用一个javax.servlet.Filter的实现。
...
requestContextFilter
org.springframework.web.filter.RequestContextFilter
requestContextFilter
/*
...
两种方式完成完全一样的功能:基于LocalThread将HTTP request对象绑定到为该请求提供服务的线程上。这使得具有request和session作用域的bean能够在后面的调用链中被访问到。 Request作用域
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,你可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。 global session作用域
也许会有一个疑问,已经通过ContextLoaderListener(或ContextLoaderServlet)将Web容器与Spring容器整合,为什么这里还要用额外的RequestContextListener以支持Bean的另外3个作用域,原因是ContextLoaderListener实现ServletContextListener监听器接口,而ServletContextListener只负责监听Web容器的启动和关闭的事件。RequestContextFilter实现ServletRequest-Listener监听器接口,该监听器监听HTTP请求事件,Web服务器接收的每次请求都会通知该监听器。通过配置RequestContext-Filter,Spring容器与Web容器结合的更加密切。
如果将Web相关作用域的Bean注入到singleton或prototype的Bean中,这种情况下,需要Spring AOP。