1.基本概念

在谍战中,谍报人员每天做的事情,实际上就是监听,监听敌方有无发报,发报能否被拦截,报文是否能够解析。而 java web中,也有监听器——Listener。

  • Listener监听器,作为javaWeb的3大组件之一,用来监听Servlet容器产生的事件并进行相应的处理。
  • 容器产生的事件分类如下:
    生命周期相关的事件。 – 如 对象的创建或者销毁等
    属性状态相关的事件。 – attribute属性的增删改
    存值状态相关的事件。 – 如 对象和session的绑定和解绑
  • 底层原理是采用接口回调的方式实现。

2.基本分类

java servlet listener 加载顺序 java中的listener_ide


HttpSessionBindingListener用于监听存值状态相关的事件。

所谓钝化,其实就是序列化操作(对象转换为字节序列),由内存写入到硬盘;

所谓活化,就是反序列化(字节序列转换成对象),指的是从硬盘到内存的过程。

3.监听器详解

(1)ServletRequestListener监听器

在ServletRequest创建和关闭时都会通知ServletRequestListener监听器。

常用方法如下:

java servlet listener 加载顺序 java中的listener_xml_02


自定义监听器:MyRequestListener.java

public class MyRequestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        System.out.println("请求销毁了...");
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        System.out.println("创建请求...");
    }
}

web.xml配置监听器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
  
  <listener>
        <listener-class>com.lagou.demo03.MyRequestListener</listener-class>
    </listener>
</web-app>

运行效果;

java servlet listener 加载顺序 java中的listener_ide_03


只要在网页发起了请求,就会调用这个监听器的两个回调方法。

(2)ServletRequestAttributeListener监听器

向ServletRequest添加、删除或者替换一个属性的时候,将会通知

ServletRequestAttributeListener监听器。

常用方法如下:

java servlet listener 加载顺序 java中的listener_java_04


MyRequestAttributeListener.java

public class MyRequestAttributeListener implements ServletRequestAttributeListener {
    @Override
    public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {
        System.out.println("增加了属性" + servletRequestAttributeEvent.getName());
    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {
        System.out.println("属性" + servletRequestAttributeEvent.getName() + "被删除了" );
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {
        System.out.println("修改属性" + servletRequestAttributeEvent.getName());
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<listener>
    <listener-class>com.example.demo03.MyRequestAttributeListener</listener-class>
</listener>
</web-app>

requestAttribute.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>实现请求中属性状态的改变</title>
</head>
<body>
<%
    // 实现属性的添加
    request.setAttribute("name", "zhangfei");
    // 实现属性的修改
    request.setAttribute("name", "guanyu");
    // 实现属性的删除
    request.removeAttribute("name");
%>
</body>
</html>

在浏览器请求requestAttribute.jsp

java servlet listener 加载顺序 java中的listener_Listener_05


打印输出:

java servlet listener 加载顺序 java中的listener_xml_06


(3)HttpSessionListener监听器

当一个HttpSession刚被创建或者失效(invalidate)的时候,将会通知HttpSessionListener监听

器。

常用方法如下:

java servlet listener 加载顺序 java中的listener_监听器_07


MySessionListener.java

public class MySessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        System.out.println("创建了session...");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        System.out.println("session销毁!");
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<listener>
    <listener-class>com.example.demo03.MySessionListener</listener-class>
</listener>

<session-config>
    <session-timeout>3</session-timeout>
</session-config>
</web-app>

这里配置了session的超时时间为3分钟,不然浏览器的session默认超时时间是30分钟,比较难等。

浏览器直接请求index.jsp,就会看到日志输出:

java servlet listener 加载顺序 java中的listener_ide_08


(4)HttpSessionAttributeListener监听器

HttpSession中添加、删除或者替换一个属性的时候,将会通知HttpSessionAttributeListener监

听器。

常用方法如下:

java servlet listener 加载顺序 java中的listener_Listener_09


MySessionAttributeListener.java

public class MySessionAttributeListener implements HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
        System.out.println("增加了属性" + httpSessionBindingEvent.getName());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
        System.out.println("属性" + httpSessionBindingEvent.getName() + "被删除!");
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
        System.out.println("修改属性" + httpSessionBindingEvent.getName());
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<listener>
    <listener-class>com.example.demo03.MySessionAttributeListener</listener-class>
</listener>
</web-app>

sessionAttribute.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>实现会话中属性状态的改变</title>
</head>
<body>
<%
    // 增加属性
    session.setAttribute("name", "caocao");
    // 修改属性
    session.setAttribute("name", "caoren");
    // 删除属性
    session.removeAttribute("name");
%>
</body>
</html>

在浏览器访问sessionAttribute.jsp,可以看到日志输出:

java servlet listener 加载顺序 java中的listener_监听器_10


(5)ServletContextListener监听器

在ServletContext创建和关闭时都会通知ServletContextListener监听器。

常用方法如下:

java servlet listener 加载顺序 java中的listener_ide_11


MyContextListener.java

public class MyContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext对象创建了...");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("销毁ServletContext对象...");
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<listener>
    <listener-class>com.example.demo03.MyContextListener</listener-class>
</listener>
</web-app>

ServletContext对象随着服务器的启动而创建 ,随着服务器的关闭而销毁,对应整个web项目。

java servlet listener 加载顺序 java中的listener_Listener_12

java servlet listener 加载顺序 java中的listener_xml_13


(6)ServletContextAttributeListener监听器

向ServletContext添加、删除或者替换一个属性的时候,将会通知

ServletContextAttributesListener监听器

常用方法如下:

java servlet listener 加载顺序 java中的listener_Listener_14


MyContextAttributeListener.java

public class MyContextAttributeListener implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("增加了属性" + servletContextAttributeEvent.getName());
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("属性" + servletContextAttributeEvent.getName() + "被删除!");
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("修改属性" + servletContextAttributeEvent.getName());
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<listener>
    <listener-class>com.lagou.demo03.MyContextAttributeListener</listener-class>
</listener>
</web-app>

contextAttribute.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>实现ServletContext对象属性的改变</title>
</head>
<body>
<%
    // 增加属性
    application.setAttribute("name", "sunquan");
    // 修改属性
    application.setAttribute("name", "zhouyu");
    // 删除属性
    application.removeAttribute("name");
%>
</body>
</html>

在浏览器访问contextAttribute.jsp,可以看到日志输出:

java servlet listener 加载顺序 java中的listener_xml_15


(7)HttpSessionBindingListener监听器

HttpSession中绑定和解除绑定时,将会通知HttpSessionListener监听器。

常用方法如下:

java servlet listener 加载顺序 java中的listener_监听器_16


要想实现session绑定对象的监听,需要建立一个实体类,还需要这个类和HttpSessionBindingListener接口关联。

Person.java

public class Person implements HttpSessionBindingListener {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {
        System.out.println("对象绑定到session中了" + httpSessionBindingEvent.getName());
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
        System.out.println("解除绑定成功!");
    }
}

sessionBind.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>实现session中对象的绑定和解除</title>
</head>
<body>
<%
    // 准备一个Person类型的对象
    Person person = new Person();
    person.setName("zhangfei");
    person.setAge(30);
    // 将对象与session对象进行绑定
    session.setAttribute("person", person);
    // 解除绑定
    session.removeAttribute("person");
%>
</body>
</html>

注意,这个监听器不需要在web.xml中配置了,因为它和类绑定到一起了。

(8)HttpSessionActivationListener监听器

当有session数值的钝化和活化操作时,将会通知HttpSessionActivationListener监听器。

常用方法如下:

java servlet listener 加载顺序 java中的listener_xml_17


Student.java

public class Student implements Serializable, HttpSessionActivationListener {
    private String name;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) {
        System.out.println("执行了钝化操作..." + httpSessionEvent.getSession());
    }

    @Override
    public void sessionDidActivate(HttpSessionEvent httpSessionEvent) {
        System.out.println("活化操作进行中...");
    }
}

既然涉及序列化,所以实体类需要实现 Serializable接口。

sessionActivate.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>实现session中数据的钝化和活化操作</title>
</head>
<body>
<%
    // 创建Student类型的对象
    Student student = new Student();
    student.setName("zhangfei");
    // 将数据放入到session中
    session.setAttribute("student", student);
%>

</body>
</html>

同样不需要在web.xml中配置,因为Student类已经和监听器关联了。

启动项目后,在浏览器中请求sessionActivate.jsp,再将服务器关闭或者重启服务器,可以看到日志输出:

java servlet listener 加载顺序 java中的listener_xml_18


此时执行了钝化操作,选中tomcat临时路径,硬盘中去访问此路径,

java servlet listener 加载顺序 java中的listener_监听器_19


然后进入work目录,找到自己的项目文件,可以看到多了一个session.ser文件,这个文件就是进行钝化操作所存储的文件。

java servlet listener 加载顺序 java中的listener_Listener_20


java servlet listener 加载顺序 java中的listener_Listener_21


ideal有个特性,启动服务的时候,会把work目录下的文件清空。如果想在下次启动项目的时候,执行活化操作(即把session保存的数据读取到内存),则需要进行session钝化文件目录的重新配置。

配置context.xml文件的方式如下:

<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="true"> <!-- 配置文件存放的路径信息,可以自由指定 --> 
<Store className="org.apache.catalina.session.FileStore" directory="C:\session"/> 
</Manager>

将上面的配置加在tomacat安装目录的context.xml文件中

java servlet listener 加载顺序 java中的listener_Listener_22


java servlet listener 加载顺序 java中的listener_Listener_23


根据这个配置,就把session钝化文件存放的目录改成了C:\session。

再次启动项目进行钝化,可以看到,重新生成了一个.session文件在新目录。

java servlet listener 加载顺序 java中的listener_ide_24


现在可以进行活化操作了,

新建一个sessionGet.jsp用于访问session钝化的数据。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>实现session中数据的钝化和活化操作</title>
</head>
<body>
<%
    // 创建Student类型的对象
    Student student = new Student();
    student.setName("zhangfei");
    // 将数据放入到session中
    session.setAttribute("student", student);
%>

</body>
</html>

重启项目,访问sessionGet.jsp,可以看到打印输出:

java servlet listener 加载顺序 java中的listener_ide_25


session数据活化成功。

4.实战案例

实现一个监听实时用户在线人数的小例子:
自定义类实现监听器接口并重写相关的方法
OnlineUser.java

public class OnlineUser implements HttpSessionListener, ServletContextListener {
    // 声明一个ServletContex类型的引用负责作为全局对象来记录当前在线用户的数量,通过属性记录
    private ServletContext servletContext = null;

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        servletContext = servletContextEvent.getServletContext();
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        servletContext = null;
    }

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        System.out.println("有新用户上线了...");
        Object count = servletContext.getAttribute("count");
        // 若当前用户为第一个用户,则将全局对象中的属性值设置为1即可
        if (null == count) {
            servletContext.setAttribute("count", 1);
        }
        // 若当前用户不是第一个用户,则将全局对象中原有的数据取出来加1后再设置进去
        else {
            Integer integer = (Integer)count;
            integer++;
            servletContext.setAttribute("count", integer);
        }
        System.out.println("当前在线用户数量为:" + servletContext.getAttribute("count"));
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        System.out.println("有用户已下线...");
    }
}

web.xml配置监听器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <listener>
        <listener-class>com.example.demo04.OnlineUser</listener-class>
    </listener>
</web-app>

创建jsp页面显示在线人数
onlineUser.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>实现当前在线的用户数量</title>
</head>
<body>
<h1>当前在线用户人数为:${applicationScope.count}</h1>
</body>
</html>

运行效果

java servlet listener 加载顺序 java中的listener_Listener_26


可以再打开一个浏览器请求

java servlet listener 加载顺序 java中的listener_ide_27


java servlet listener 加载顺序 java中的listener_java_28