监听器
概念
监听器用于监听web应用中某些对象信息的创建、销毁、增加,修改,删除等动作的
发生,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器自动调用
监听器对象中的方法。
常用于统计在线人数和在线用户,系统加载时进行信息初始化,统计网站的访问量等。
创建步骤
- 创建类
 - 实现指定的监听器接口中的方法
 - 在web.xml文件中配置监听或在类上标注@WebListener 注解
 
第一类:域对象监听器
监听域对象 创建与销毁的监听器
| 监听器接口 | 描述 | 
|---|---|
| ServletContextListener | 监听Servlet上下文对象的创建、销毁 | 
| HttpSessionListener | 监听会话对象的创建、销毁 | 
| ServletRequestListener | 监听请求对象的创建、销毁 | 
1.请求对象的创建和销毁的监听器
生命周期:
创建:客户端发送请求,Tomcat会创建请求对象
销毁:服务器返回响应,Tomcat会将请求对象销毁
public class MyServletRequestListener implements ServletRequestListener {
    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        //获取请求对象
        ServletRequest request = servletRequestEvent.getServletRequest();
        System.out.println("请求对象被创建了:" + request);
    }
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        //获取请求对象
        ServletRequest request = servletRequestEvent.getServletRequest();
        System.out.println("请求对象被销毁了:" + request);
    }
}
 
<!-- web.xml中配置 -->
    <listener>
        <listener-class>com.ckl.listener.MyServletRequestListener</listener-class>
    </listener>
 
实验:再写两个页面html,jsp类型,启动服务器多次访问
 请求对象可以存临时数据,存在时间短
 
2.全局域对象的创建和销毁的监听器
生命周期:
创建:项目启动时
销毁:服务器正常关闭时
经验:让整个项目共享的数据可以存储在全局域对象里
public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext servletContext = servletContextEvent.getServletContext();
        //获取全局域对象的初始化参数 -- 注意:在web.xml中配置
        String code = servletContext.getInitParameter("code");
        System.out.println("全局域对象被创建了:" + servletContext + " -- " + code);
        //设置网站在线人数【共享】
        servletContext.setAttribute("count",0);
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        ServletContext servletContext = servletContextEvent.getServletContext();
        System.out.println("全局域对象被销毁了:" + servletContext);
    }
}
 
<!-- web.xml中配置 -->
<!--配置全局域对象的初始化参数-->
    <context-param>
        <param-name>code</param-name>
        <param-value>UTF-8</param-value>
    </context-param>
    <listener>
        <listener-class>com.qf.listener01.MyServletContextListener</listener-class>
    </listener>
 
3.会话对象的创建和销毁的监听器
生命周期:
创建:服务端使用到会话对象才会被创建
销毁:会话域对象过期时间到后会被销毁
注意:
1.客户端访问服务器里的html页面不会创建会话对象,因为没有使用到session
2.客户端访问服务器里的jsp页面会创建会话对象,因为使jsp里9大内置对象里有session
3.会话域对象过期时间默认30分钟
4.在web.xml中可以设置session的过期时间(时间单位:分钟),注意:最后一次使用session的时间往后移(闲置时间)经验:
session对象在服务器中存储,默认30分钟过期时间
JSESSIONID存储在客户端的Cookie中,默认过期时间是会话结束时
这样会导致,关闭客户端后,session对象还存活在服务端,相同的客户端再次开启并发送请求访问session时就会创建新的session对象,原有的session对象就视作为占用资源的无效对象
我们可以设置JSESSIONID的过期时间,用户关闭客户端再次开启后才能找到之前在服务器对应的session对象
public class MyHttpSessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println("会话域对象被创建了:" + session.getId());
        //获取全局域对象
        ServletContext servletContext = session.getServletContext();
        //更新网站在线人数
        int count = (Integer) servletContext.getAttribute("count");
        count++;
        servletContext.setAttribute("count",count);
        System.out.println(count);
    }
    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println("会话域对象被销毁了:" + session.getId());
        //获取全局域对象
        ServletContext servletContext = session.getServletContext();
        //更新网站在线人数
        int count = (Integer) servletContext.getAttribute("count");
        count--;
        servletContext.setAttribute("count",count);
        System.out.println(count);
    }
}
 
<!-- web.xml中配置 -->
    <listener>
        <listener-class>com.qf.listener01.MyHttpSessionListener</listener-class>
    </listener>
	<!--设置过期时间-->
    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>
 
第二类:属性监听器
监听域对象属性变化的监听器
| 监听器接口 | 描述 | 
|---|---|
| ServletContextAttributeListener | 监听Servlet上下文对象属性的创建、删除、替换 | 
| HttpSessionAttributeListener | 监听会话对象属性的创建、删除、替换 | 
| ServletRequestAttributeListener | 监听请求对象属性的创建、删除、替换 | 
Servlet上下文对象属性变化的监听器,其他同理实现接口生成方法
注意配置方式,注解用@WebListener
@WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
        ServletContext servletContext = servletContextAttributeEvent.getServletContext();
        String name = servletContextAttributeEvent.getName();
        Object value = servletContextAttributeEvent.getValue();
        System.out.println(servletContext + "全局域中的属性添加了:" + name + " -- " + value);
    }
    @Override
    public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
        ServletContext servletContext = servletContextAttributeEvent.getServletContext();
        String name = servletContextAttributeEvent.getName();
        Object value = servletContextAttributeEvent.getValue();
        System.out.println(servletContext + "全局域中的属性删除了:" + name + " -- " + value);
    }
    @Override
    public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
        ServletContext servletContext = servletContextAttributeEvent.getServletContext();
        String name = servletContextAttributeEvent.getName();
        Object value = servletContextAttributeEvent.getValue();//获取被替换的值
        System.out.println(servletContext + "全局域中的属性替换了:" + name + " -- " + value);
    }
}
 
不同在于域对象名字不同application,request、session,其他调用都与获取的相同,其中session可以获取id
    <%
        //操作全局域对象的属性
        application.setAttribute("msg","aaa");//添加属性
        application.setAttribute("msg","bbb");//替换属性
        application.removeAttribute("msg");//删除属性
    %>
 
第三类:监听HttpSession中的对象(JavaBean)
前两类监听器是作用在 ServletContext HttpSession ServletRequest上
第三类监听器是作用在JavaBean上的。
注意:这类监听器不需要在web.xml中配置
| 监听器接口 | 描述 | 
|---|---|
| HttpSessionBindingListener | 监听会话对象中JavaBean对象的绑定、删除 | 
| HttpSessionActivationListener | 监听会话对象中JavaBean对象的钝化、活化 | 
钝化(序列化):在内存中JavaBean对象通过Session存储硬盘的过程
活化(反序列化):从硬盘中通过Session取出JavaBean对象到内存的过程
1.理解
HttpSessionBindingListener
理解:监听该类(User)对象在session中的绑定(添加)和解绑(删除)
valueBound – 绑定方法
valueUnbound – 解绑方法
HttpSessionActivationListener
理解:监听该类(User)对象在session中的钝化(序列化)和活化(反序列化)
sessionWillPassivate – 钝化(序列化):将类(User)对象写入到文件
sessionDidActivate – 活化(反序列化):将文件里的对象读取到程序中
注意:
实现session的钝化和活化底层使用到了ObjectInputStream、ObjectOutputStream
钝化和活化的JavaBean对象必须实现Serializable(序列化接口)
注意: 想要随着Session 被钝化、活化的对象它的类必须实现Serializable 接口,放在
Session中没有实现Serilizable接口的对象,在Session钝化时,不会被序列化到磁盘上。
public class User implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable {
    private String username;
    private transient String password;//transient修饰,不会被写到硬盘
    private String name;
//生成方法略
    
    @Override
    public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {
        HttpSession session = httpSessionBindingEvent.getSession();
        String name = httpSessionBindingEvent.getName();
        Object value = httpSessionBindingEvent.getValue();
        System.out.println(session.getId() + "会话对象中绑定了:" + name + " -- " + value);
    }
    @Override
    public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
        HttpSession session = httpSessionBindingEvent.getSession();
        String name = httpSessionBindingEvent.getName();
        Object value = httpSessionBindingEvent.getValue();
        System.out.println(session.getId() + "会话对象中解绑了:" + name + " -- " + value);
    }
    @Override
    public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println(session.getId() + "的User对象被钝化了:" + this);
    }
    @Override
    public void sessionDidActivate(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println(session.getId() + "的User对象被活化了:" + this);
    }
}
 
当对象被绑定到HttpSession对象中时,web服务器调用该对象的valueBound方法
当对象从HttpSession对象中解除绑定时,web服务器调用该对象的valueUnbound方法
javabean对象将要随Session对象被钝化(序列化)之前,web服务器调用该对象的sessionWillPassivate 方法
这样javabean对象就可以知道自己将要和Session对象一起被钝化到硬盘中
javabean对象将要随Session对象被活化(反序列化)之后,web服务器调用该对象的sessionDidActive方法
这样javabean对象就可以知道自己将要和Session对象一起被活化回到内存中
<%@ page import="com.ckl.listener03.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>page02.jsp</h1>
        <%
            session.setAttribute("user",new User("qnz","123","奇男子"));
//            session.removeAttribute("user");
            //session还可以添加其他数据
            session.setAttribute("obj",new Object());
            session.setAttribute("msg","胡子");
        %>
</body>
</html>
 
序列化测试还需在WebContent\META-INF文件夹下创建一个context.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<Context>
	<!-- 
		maxIdleSwap:"1": session如果1分钟没有使用就序列化
		directory: 序列化后文件所保存的路径 
	-->
	<Manager className="org.apache.catalina.session.PersistentManager"
		maxIdleSwap="1">
		<Store className="org.apache.catalina.session.FileStore" 		
			directory="C:\\text" />
	</Manager>
</Context>	
 
2.面试题
面试题1
Session中的JavaBean对象什么情况下会钝化
session未过期
JavaBean对象所属的类必须实现Serializable接口
服务器正常关闭
以上三个条件必须都满足
面试题2
Session中的JavaBean对象什么情况下不会钝化
1.session对象过期
2.session.invalidate();
3.服务器非正常关闭
以上三个条件满足一个即可
面试题3
Session中的JavaBean对象什么情况下不会活化
服务器启动,但是session对象过期了
解释
Session 的钝化与活化
- 钝化:当服务器正常关闭时,还存活着的session(在设置时间内没有销毁) 会随着服务
 器的关闭被以文件(“SESSIONS.ser”)的形式存储在tomcat 的work 目录下,这个过程叫
做Session 的钝化。
- 活化:当服务器再次正常开启时,服务器会找到之前的“SESSIONS.ser” 文件,从中恢
 复之前保存起来的Session 对象,这个过程叫做Session的活化。
- 注意事项
 
- 想要随着Session 被钝化、活化的对象它的类必须实现Serializable 接口,还有的是只有在服务器正常关闭的条件下,还未超时的Session 才会被钝化成文件。当Session 超时、调用invalidate方法或者服务器在非正常情况下关闭时,Session 都不会被钝化,因此也就不存在活化。
 - 在被钝化成“SESSIONS.ser” 文件时,不会因为超过Session 过期时间而消失,这个文件会一直存在,等到下一次服务器开启时消失。
 - 当多个Session 被钝化时,这些被钝化的Session 都被保存在一个文件中,并不会为每个Session 都建立一个文件。
 



















