监听器Listener汇总

本节对监听器进行一次系统的了解学习。模块分为:
1.什么是Listener
2.Listener的分类,
3.平时重点使用的ServletContextListener监听器的使用细节
4.几个demo
5.spring中使用的Listener介绍
6.项目启动时spring容器与servlet监听器加载顺序及影响的实例说明。



这里参考了以下博主的文章:
a. fjdingsd的文章: 如何在自定义Listener(监听器)中使用Spring容器管理的bean.

b. 不知道名字的博主的文章: 如何在Listener(监听器)中使用spring容器管理的bean

c. OpenFire_的文章: Listener 监听器
这里的很多内容节选至这些博文,感谢你们~


一. 什么是监听器Listener?

Listener 是servlet的监听器,可以监听客户端的请求,服务端的操作等。
监听器Listener在application,session,request三个对象创建、销毁或者往其中添加,修改,删除属性时会自动执行代码的功能组件。

二. Listener的分类与方法说明
 主要有以下三类:
 1、ServletContext监听类
 

1.1 ServletContextListener接口:用于对Servlet整个上下文进行监听(创建、销毁)。*最重要,最常用的监听类。其类下有几个重要的方法

//上下文初始化  *****应用最广的一个方法。
public void contextInitialized(ServletContextEvent sce);
//上下文销毁
public void contextDestroyed(ServletContextEvent sce);

//ServletContextEvent事件:得到一ServletContext(application)对象
public ServletContext getServletContext();

1.2 ServletContextAttributeListener接口:对Servlet上下文属性的监听(增删改属性)。其类下有几个重要方法:

//增加属性
public void attributeAdded(ServletContextAttributeEvent scab);  
//属性删除
public void attributeRemoved(ServletContextAttributeEvent scab);
//属性替换(第二次设置同一属性)
public void attributeRepalced(ServletContextAttributeEvent scab);

//ServletContextAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值

2、Session监听类 Session属于http协议下的内容,接口位于javax.servlet.http.*包下。

2.1 HttpSessionListener接口:对Session的整体状态的监听。其下有几个重要方法

public void sessionCreated(HttpSessionEvent se);//session创建
public void sessionDestroyed(HttpSessionEvent se);//session销毁

//HttpSessionEvent事件:
public HttpSession getSession();//取得当前操作的session

2.2 HttpSessionAttributeListener接口:对session的属性监听。其下有几个重要方法

public void attributeAdded(HttpSessionBindingEvent se);//增加属性
public void attributeRemoved(HttpSessionBindingEvent se);//删除属性
public void attributeReplaced(HttpSessionBindingEvent se);//替换属性

//HttpSessionBindingEvent事件:
public String getName();//取得属性的名称
public Object getValue();//取得属性的值
public HttpSession getSession();//取得当前的session

2.3 session的销毁有两种情况

1、session超时,web.xml配置:
<session-config>
        <!--session120分钟后超时销毁-->
        <session-timeout>120</session-timeout>
</session-config>
2、手工使session失效
public void invalidate();//使session失效方法。session.invalidate();

3、Request监听类

3.1 ServletRequestListener接口:用于对Request请求进行监听(创建、销毁)。其下有几个重要方法:

//request初始化
public void requestInitialized(ServletRequestEvent sre);
//request销毁
public void requestDestroyed(ServletRequestEvent sre);
//ServletRequestEvent事件:

//取得一个ServletRequest对象
public ServletRequest getServletRequest();
//取得一个ServletContext(application)对象
public ServletContext getServletContext();

3.2 ServletRequestAttributeListener接口:对Request属性的监听(增删改属性)。其下有几个重要方法

//增加属性
public void attributeAdded(ServletRequestAttributeEvent srae);
//属性删除
public void attributeRemoved(ServletRequestAttributeEvent srae);
//属性替换(第二次设置同一属性)
public void attributeReplaced(ServletRequestAttributeEvent srae);

//ServletRequestAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值

4、自定义监听器除了在定义类时实现对应监听其接口,在web.xml中配置也要自定义监听器 **很重要

Listener配置信息必须在Filter和Servlet配置之前,不然启动会报错。Listener的初始化(ServletContentListener初始化)比Servlet和Filter都优先,而销毁比Servlet和Filter都慢。

<listener>
    <!--自定义类的路径 -->
    <listener-class>com.zlf.webserviceListener</listener-class>
</listener>

三.重点说一下 ServletContextListener.

1.在web.xml中的加载顺序

首先可以肯定的是,加载顺序与它们在 web.xml 文件中的先后顺序无关。即不会因为 filter 写在 listener 的前面而会先加载 filter。最终得出的结论是:listener -> filter -> servlet 。同时还存在着这样一种配置节:context-param,它用于向 ServletContext 提供键值对,即应用程序上下文信息。
我们的 listener, filter 等在初始化时会用到这些上下文中的信息,那么 context-param 配置节是不是应该写在 listener 配置节前呢?实际上 context-param 配置节可写在任意位置,因此真正的加载顺序为:context-param -> listener -> filter -> servlet 对于某类配置节而言,与它们出现的顺序是有关的。
以 filter 为例,web.xml 中当然可以定义多个 filter,与 filter 相关的一个配置节是 filter-mapping,这里一定要注意,对于拥有相同 filter-name 的 filter 和 filter-mapping 配置节而言,filter-mapping 必须出现在 filter 之后,否则当解析到 filter-mapping 时,它所对应的 filter-name 还未定义。
web 容器启动时初始化每个 filter 时,是按照 filter 配置节出现的顺序来初始化的,当请求资源匹配多个 filter-mapping 时,filter 拦截资源是按照 filter-mapping 配置节出现的顺序来依次调用 doFilter() 方法的。 servlet 同 filter 类似 ,此处不再赘述。
由此,可以看出,web.xml 的加载顺序是:context-param -> listener -> filter -> servlet ,而同个类型之间的实际程序调用的时候的顺序是根据对应的mapping 的顺序进行调用的。

2.ServletContextListener 是什么:从上面加载顺序分享一下,ServletContextListener 就是一个监听器

ServletContext 被 Servlet 程序用来与 Web 容器通信。例如写日志,转发请求。每一个 Web 应用程序含有一个Context,被Web应用内的各个程序共享。因为Context可以用来保存资源并且共享,所以我所知道的 ServletContext 的最大应用是Web缓存—-把不经常更改的内容读入内存,所以服务器响应请求的时候就不需要进行慢速的磁盘I/O了。
ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化,如服务器启动时 ServletContext 被创建,服务器关闭时 ServletContext 将要被销毁。在JSP文件中,application 是 ServletContext 的实例,由JSP容器默认创建。Servlet 中调用 getServletContext()方法得到 ServletContext 的实例。

3.ServletContextListener 能够做些什么:缓存设计,服务开启,注册,等一切想在项目启动时的操作。

四. Listener应用实例

1、利用HttpSessionListener统计最多在线用户人数,分为两步,第一在web.xml中声明自定义监听类。第二步在自定义监听类实现HttpSessionListener 接口。

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class HttpSessionListenerImpl implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count++;
        app.setAttribute("onLineCount", count);
        int maxOnLineCount = Integer.parseInt(app.getAttribute("maxOnLineCount").toString());
        if (count > maxOnLineCount) {
            //记录最多人数是多少
            app.setAttribute("maxOnLineCount", count);
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //记录在那个时刻达到上限
            app.setAttribute("date", df.format(new Date()));
        }
    }
    //session注销、超时时候调用,停止tomcat不会调用
    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count--;
        app.setAttribute("onLineCount", count);    

    }
}

2.利用ServletContextListener 项目启动时执行一些初始化操作,例如从数据库加载全局配置文件,启动服务等。这里我们是web项目启动webservice服务。
分为两步,第一在web.xml中声明自定义监听类。第二不在自定义监听类实现ServletContextListener接口。
step1 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>WebServiceDemo001</display-name>
<!--   引入自定义监听器类,要实现ServletContextListener接口 -->
  <listener>
    <listener-class>com.zlf.TestWebService</listener-class>
  </listener>
 <listener>
      <listener-class>com.zlf.HttpSessionListenerImpl </listener-class>
</listener>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

step2 自定义监听类

import javax.jws.WebParam;
import javax.jws.WebService;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.xml.ws.Endpoint;
/**
 * 学习一招:如果用的是web项目,不是maven 项目,放入jar包到lib下时,如果系统自动变奶瓶,则buildpath时remove了,不然启动报错。这个很重要
 * @author Administrator
 *
 */
@WebService
public class TestWebService implements ServletContextListener{

    public String sayHello(@WebParam(name="name")String name) {
        return "Hello," + name;
    }

    public void contextInitialized(ServletContextEvent arg0) {
        System.out.println("开始启动服务......");
        Endpoint.publish("http://localhost:9999/TestWebService",
                new TestWebService());
        System.out.println("服务启动成功!");
    }

    public void contextDestroyed(ServletContextEvent arg0) {

    }

}

五.spring相关的Listener和配置说明。

5.1 ContextLoaderListener – Spring使用ContextLoaderListener加载ApplicationContext配置信息

ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。
ContextLoaderListener如何查找ApplicationContext.xml的配置位置以及配置多个xml:如果在web.xml中不写任何参数配置信息,默认的路径是”/WEB-INF/applicationContext.xml”,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml(在MyEclipse中把xml文件放置在src目录下)。如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数。

<context-param>
        <param-name>contextConfigLocation</param-name>
<!-- 采用的是通配符方式,查找WEB-INF/spring目录下xml文件。如有多个xml文件,以“,”分隔。 -->
        <param-value>classpath:spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

5.2 Log4jConfigListener – Spring使用Log4jConfigListener配置Log4j日志

Spring使用Log4jConfigListener的好处:
1.动态的改变记录级别和策略,不需要重启Web应用。
2.把log文件定在 /WEB-INF/logs/ 而不需要写绝对路径。因为系统把web目录的路径压入一个叫webapp.root的系统变量。这样写log文件路径时不用写绝对路径了。
3.可以把log4j.properties和其他properties一起放在/WEB-INF/ ,而不是Class-Path。
4.设置log4jRefreshInterval时间,开一条watchdog线程每隔段时间扫描一下配置文件的变化。

<context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>project.root</param-value><!-- 用于定位log文件输出位置在web应用根目录下,log4j配置文件中写输出位置:log4j.appender.FILE.File=${project.root}/logs/project.log -->
</context-param>
<context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath:log4j.properties</param-value><!-- 载入log4j配置文件 -->
</context-param>
<context-param>
        <param-name>log4jRefreshInterval</param-name>
        <param-value>60000</param-value><!--Spring刷新Log4j配置文件的间隔60秒,单位为millisecond-->
</context-param>
<listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

5.3 IntrospectorCleanupListener – Spring使用IntrospectorCleanupListener清理缓存

这个监听器的作用是在web应用关闭时刷新JDK的JavaBeans的Introspector缓存,以确保Web应用程序的类加载器以及其加载的类正确的释放资源。
如果JavaBeans的Introspector已被用来分析应用程序类,系统级的Introspector缓存将持有这些类的一个硬引用。因此,这些类和Web应用程序的类加载器在Web应用程序关闭时将不会被垃圾收集器回收!而IntrospectorCleanupListener则会对其进行适当的清理,已使其能够被垃圾收集器回收。
唯一能够清理Introspector的方法是刷新整个Introspector缓存,没有其他办法来确切指定应用程序所引用的类。这将删除所有其他应用程序在服务器的缓存的Introspector结果。
在使用Spring内部的bean机制时,不需要使用此监听器,因为Spring自己的introspection results cache将会立即刷新被分析过的JavaBeans Introspector cache,而仅仅会在应用程序自己的ClassLoader里面持有一个cache。虽然Spring本身不产生泄漏,注意,即使在Spring框架的类本身驻留在一个“共同”类加载器(如系统的ClassLoader)的情况下,
也仍然应该使用使用IntrospectorCleanupListener。在这种情况下,这个IntrospectorCleanupListener将会妥善清理Spring的introspection cache。
应用程序类,几乎不需要直接使用JavaBeans Introspector,所以,通常都不是Introspector resource造成内存泄露。相反,许多库和框架,不清理Introspector,例如: Struts和Quartz。需要注意的是一个简单Introspector泄漏将会导致整个Web应用程序的类加载器不会被回收!这样做的结果,将会是在web应用程序关闭时,该应用程序所有的静态类资源(比如:单实例对象)都没有得到释放。而导致内存泄露的根本原因其实并不是这些未被回收的类!
注意:IntrospectorCleanupListener应该注册为web.xml中的第一个Listener,在任何其他Listener之前注册,比如在Spring’s ContextLoaderListener注册之前,才能确保IntrospectorCleanupListener在Web应用的生命周期适当时机生效。

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

六.项目启动时spring容器与servlet监听器加载顺序及影响的实例说明。

1.在java web项目中我们通常会有这样的需求:当项目启动时执行一些初始化操作,例如从数据库加载全局配置文件等,通常情况下我们会用javaee规范中的Listener去实现,例如:

public class ConfigListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
          //执行初始化操作
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

当servlet容器初始化完成后便会调用contextInitialized方法。但是通常我们在执行初始化的过程中会调用service和dao层提供的方法,而现在web项目通常会采用spring框架来管理和装配bean,我们想当然会像下面这么写,假设执行初始化的过程中需要调用ConfigService的initConfig方法,而ConfigService由spring容器管理标有@Service注解.

public class ConfigListener implements ServletContextListener {

    @Autowired
    private ConfigService configService;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        configService.initConfig();
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

上面的代码会在项目启动时抛出空指针异常!ConfigService实例并没有成功注入。这是为什么呢?要理解这个问题,首先要区分Listener的生命周期和spring管理的bean的生命周期。

(1)Listener的生命周期是由servlet容器(例如tomcat)管理的,项目启动时上例中的ConfigListener是由servlet容器实例化并调用其contextInitialized方法,
而servlet容器并不认得@Autowired注解,因此导致ConfigService实例注入失败。
(2)而spring容器中的bean的生命周期是由spring容器管理的。

那么该如何在spring容器外面获取到spring容器bean实例的引用呢?这就需要用到spring为我们提供的WebApplicationContextUtils工具类,该工具类的作用是获取到spring容器的引用,进而获取到我们需要的bean实例。代码如下:

public class ConfigListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {   
        ConfigService configService = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(ConfigService.class);
        configService.initConfig();
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }

}

以上代码有一个前提,那就是servlet容器在实例化ConfigListener并调用其方法之前,要确保spring容器已经初始化完毕!而spring容器的初始化也是由Listener(ContextLoaderListener)完成,因此只需在web.xml中先配置初始化spring容器的Listener,然后在配置自己的Listener,配置如下:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
</context-param>

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

<listener>
    <listener-class>example.ConfigListener</listener-class>
</listener>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万米高空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值