参考文章:https://www.cnblogs.com/xdp-gacl/p/3969249.html
1. 概述
监听器,字面上的理解就是监听观察某个事件(程序)的发生情况,当被监听的事件真的发生了的时候,事件发生者(事件源) 就会给注册该事件的监听者(监听器)发送消息,告诉监听者某些信息,同时监听者也可以获得一份事件对象,根据这个对象可以获得相关属性和执行相关操作。
监听器模型涉及以下三个对象,模型图如下:
-
事件:
用户对组件的一个操作,或者说程序执行某个方法,称之为一个事件
如机器人程序执行工作。 -
事件源:
发生事件的组件就是事件源,也就是被监听的对象
如机器人可以工作,可以跳舞,那么就可以把机器人看做是一个事件源。 -
事件监听器(处理器):
监听并负责处理事件的方法
如监听机器人工作情况,在机器人工作前后做出相应的动作,或者获取机器人的状态信息。
所以可以理解为:监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。
监听器其实就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。
2. 监听器执行过程
- 给事件源注册监听器。
- 组件接受外部作用,也就是事件被触发。
- 组件产生一个相应的事件对象,并把此对象传递给与之关联的事件处理器。
- 事件处理器启动,并执行相关的代码来处理该事件。
3. 监听器模式
事件源注册监听器之后,当事件源触发事件,监听器就可以回调事件对象的方法。
更形象地说,监听者模式是基于:注册-回调的事件/消息通知处理模式,就是被监控者将消息通知给所有监控者。
- 注册监听器:事件源.setListener。
- 回调:事件源实现onListener。
下面是一个模仿监听器的demo,需求:实现机器人工作和跳舞,在机器人开始工作和跳舞之前输出相关提示。
首先创建一个事件源Robot:
package com.tp.models.entity;
import com.tp.event.RobotEvent;
import com.tp.listners.RobotListener;
/**
* FileName: Robot
* Author: TP
* Description:事件源:机器人
*/
public class Robot {
private RobotListener listener;
/**
* 注册机器人监听器
*
* @param listener 机器人监听器
*/
public void registerListener(RobotListener listener) {
this.listener = listener;
}
/**
* 工作
*/
public void working() {
if (listener != null) {
RobotEvent robotEvent = new RobotEvent(this);
this.listener.working(robotEvent);
}
System.out.println("机器人开始工作......");
}
/**
* 跳舞
*/
public void dancing() {
if (listener != null) {
RobotEvent robotEvent = new RobotEvent(this);
this.listener.dancing(robotEvent);
}
System.out.println("机器人开始跳舞......");
}
}
创建事件对象RobotEvent:
package com.tp.event;
import com.tp.models.entity.Robot;
/**
* FileName: RobotEvent
* Author: TP
* Description:事件对象
*/
public class RobotEvent {
private Robot robot;
public RobotEvent() {
super();
}
public RobotEvent(Robot robot) {
super();
this.robot = robot;
}
public Robot getRobot() {
return robot;
}
public void setRobot(Robot robot) {
this.robot = robot;
}
}
创建事件监听器接口RobotListener:
package com.tp.listners;
import com.tp.event.RobotEvent;
/**
* FileName: RobotListener
* Author: TP
* Description:机器人事件监听器
*/
public interface RobotListener {
/**
* 监听工作
* @param robotEvent 事件对象
*/
void working(RobotEvent robotEvent);
/**
* 监听跳舞
* @param robotEvent 事件对象
*/
void dancing(RobotEvent robotEvent);
}
实现事件监听器RobotListener:
package com.tp.listners;
import com.tp.event.RobotEvent;
import com.tp.models.entity.Robot;
/**
* FileName: RobotListenerImpl
* Author: TP
* Description:
*/
public class RobotListenerImpl implements RobotListener {
@Override
public void working(RobotEvent robotEvent) {
Robot robot = robotEvent.getRobot();
System.out.println("机器人工作提示:您的机器人已经开始工作啦!");
}
@Override
public void dancing(RobotEvent robotEvent) {
Robot robot = robotEvent.getRobot();
System.out.println("机器人跳舞提示:您的机器人已经开始跳舞啦!");
}
}
编写测试类:
package com.tp.test.listener;
import com.tp.listners.RobotListenerImpl;
import com.tp.models.entity.Robot;
import org.junit.Test;
/**
* FileName: RobotListenerTest
* Author: TP
* Description:监听器测试
*/
public class RobotListenerTest {
@Test
public void robotListenerTest(){
Robot robot = new Robot();
robot.registerListener(new RobotListenerImpl());
robot.working();
robot.dancing();
}
}
控制台输出效果:
4. JavaWeb中的监听器
4.1 基本概念
JavaWeb中的监听器是Servlet规范中定义的一种特殊类,它用于监听web应用程序中的ServletContext,HttpSession和 ServletRequest等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。
4.2 Servlet监听器的分类
在Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别为ServletContext,HttpSession和ServletRequest这三个域对象
Servlet规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型:
- 监听域对象自身的创建和销毁的事件监听器。
- 监听域对象中的属性的增加和删除的事件监听器。
- 监听绑定到HttpSession域中的某个对象的状态的事件监听器。
4.3 Servlet 8大监听器:
JavaWeb中有八大监听器
其中三大域对象各有一个生命周期监听器和属性操作监听器,共计6个;还有2个与HttpSession相关的感知型监听器。
事件源(监听对象):三大域
ServletContext域的监听器
-
生命周期监听器:ServletContextListener
ServletContextListener接口用于监听ServletContext对象的创建和销毁事件。
实现了ServletContextListener接口的类都可以对ServletContext对象的创建和销毁进行监听。
当ServletContext对象被创建时,激发contextInitialized (ServletContextEvent sce)
方法。
当ServletContext对象被销毁时,激发contextDestroyed(ServletContextEvent sce)
方法。
ServletContext域对象创建和销毁时机:
创建:服务器启动针对每一个Web应用创建ServletContext
销毁:服务器关闭前先关闭代表每一个web应用的ServletContext -
属性监听器:ServletContextAttributeListener
用于监听域对象属性的增加,删除和替换的事件,它有三个方法:
void attributeAdded(ServletContextAttributeEvent event)
:添加属性时调用;
void attributeReplaced(ServletContextAttributeEvent event)
:替换属性时调用;
void attributeRemoved(ServletContextAttributeEvent event)
:移除属性时调用;
HttpSession域的监听器
-
生命周期监听器:HttpSessionListener
HttpSessionListener 接口用于监听HttpSession对象的创建和销毁
创建一个Session时,激发sessionCreated (HttpSessionEvent se)
方法
销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se)
方法。 -
属性监听器:HttpSessioniAttributeListener
用于监听域对象属性的增加,删除和替换的事件,它有三个方法:
void attributeAdded(HttpSessionBindingEvent event)
:添加属性时调用
void attributeReplaced(HttpSessionBindingEvent event)
:替换属性时调用
void attributeRemoved(HttpSessionBindingEvent event)
:移除属性时调用
ServletRequest域的监听器
-
生命周期监听器:ServletRequestListener
ServletRequestListener接口用于监听ServletRequest 对象的创建和销毁
Request对象被创建时,监听器的requestInitialized(ServletRequestEvent sre)
方法将会被调用
Request对象被销毁时,监听器的requestDestroyed(ServletRequestEvent sre)
方法将会被调用
ServletRequest域对象创建和销毁时机:
创建:用户每一次访问都会创建request对象
销毁:当前访问结束,request对象就会销毁 -
属性监听器:ServletRequestAttributeListener
用于监听域对象属性的增加,删除和替换的事件,它有三个方法:
void attributeAdded(ServletRequestAttributeEvent srae)
:添加属性时调用
void attributeReplaced(ServletRequestAttributeEvent srae)
:替换属性时调用
void attributeRemoved(ServletRequestAttributeEvent srae)
:移除属性时调用
感知型监听器(都与HttpSession相关)
保存在Session域中的对象可以有多种状态:
- 绑定(session.setAttribute("bean",Object))到Session中
- 从 Session域中解除(session.removeAttribute("bean"))绑定
- 随Session对象持久化到一个存储设备中
- 随Session对象从一个存储设备中恢复
Servlet 规范中定义了两个特殊的监听器接口"HttpSessionBindingListener和HttpSessionActivationListener"来帮助JavaBean 对象了解自己在Session域中的这些状态:
-
HttpSessionBindingListener
添加到javabean上,javabean就知道自己是否添加到session中了。 -
HttpSessionActivationListener
Tomcat在钝化和活化session时,javabean感知到了后调用
它用来添加到JavaBean上,而不是添加到三大域上,注意⚠️:这两个监听器都不需要在web.xml中注册!
5. JavaWeb监听器使用示例
5.1 ServletContextListener监听器范例:
ServletContextListener接口用于监听ServletContext对象的创建和销毁事件。
范例:编写一个MyServletContextListener类,实现ServletContextListener接口,监听ServletContext对象的创建和销毁:
package com.tp.listners;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* FileName: MyServletContextListener
* Author: TP
* Description:MyServletContextListener类实现了ServletContextListener接口,
* 因此可以对ServletContext对象的创建和销毁这两个动作进行监听。
*/
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext对象创建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext对象销毁");
}
}
在web.xml文件中注册监听器
要想监听事件源,那么必须将监听器注册到事件源上才能够实现对事件源的行为动作进行监听,在JavaWeb中,监听的注册是在web.xml文件中进行配置的,如下:
<!-- 注册针对ServletContext对象进行监听的监听器 -->
<listener>
<description>ServletContextListener监听器</description>
<!--实现了ServletContextListener接口的监听器类 -->
<listener-class>com.tp.listeners.MyServletContextListener</listener-class>
</listener>
启动项目,控制台输出:
5.2 HttpSessionListener监听器范例:
HttpSessionListener接口用于监听HttpSession对象的创建和销毁
范例:编写一个MyHttpSessionListener类,实现HttpSessionListener接口,监听HttpSession对象的创建和销毁:
package com.tp.listeners;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* FileName: MyHttpSessionListener
* Author: TP
* Date: 2020-01-09 08:47
* Description:MyHttpSessionListener类实现了HttpSessionListener接口,
* 因此可以对HttpSession对象的创建和销毁这两个动作进行监听。
*/
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println(httpSessionEvent.getSession() + "创建了!!");
}
/*
HttpSession的销毁时机需要在web.xml中进行配置,如下:
<session-config>
<session-timeout>1</session-timeout>
</session-config>
这样配置就表示session在1分钟之后就被销毁
*/
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("session销毁了!!");
}
}
web.xml配置:
<!--注册针对HttpSession对象进行监听的监听器-->
<listener>
<description>HttpSessionListener监听器</description>
<listener-class>com.tp.listeners.MyHttpSessionListener</listener-class>
</listener>
<!-- 配置HttpSession对象的销毁时机 -->
<session-config>
<!--配置HttpSession对象的1分钟之后销毁 -->
<session-timeout>1</session-timeout>
</session-config>
当我们访问jsp页面时,HttpSession对象就会创建,此时就可以在HttpSessionListener观察到HttpSession对象的创建过程了,我们可以写一个jsp页面观察HttpSession对象创建的过程。
启动项目访问localhost:8080,观察控制台输出如下:
1分钟后Session自动销毁:
5.3 ServletRequestListener监听器范例:
ServletRequestListener接口用于监听ServletRequest 对象的创建和销毁
范例:编写一个MyServletRequestListener类,实现ServletRequestListener接口,监听ServletRequest对象的创建和销毁:
package com.tp.listeners;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
/**
* FileName: MyServletRequestListener
* Author: TP
* Description: MyServletRequestListener类实现了ServletRequestListener接口,
* 因此可以对ServletRequest对象的创建和销毁这两个动作进行监听。
*/
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "销毁了!!");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "创建了!!");
}
}
在web.xml文件中注册监听器:
<!--注册针对ServletRequest对象进行监听的监听器-->
<listener>
<description>ServletRequestListener监听器</description>
<listener-class>com.tp.listeners.MyServletRequestListener</listener-class>
</listener>
控制台效果:
5.4 ServletContextAttributeListener监听器范例:
编写ServletContextAttributeListener监听器监听ServletContext域对象的属性值变化情况,代码如下:
package com.tp.listeners;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import java.text.MessageFormat;
/**
* FileName: MyServletContextAttributeListener
* Author: TP
* Description: ServletContext域对象中属性的变更的事件监听器
*/
public class MyServletContextAttributeListener implements
ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent scab) {
String str = MessageFormat.format(
"ServletContext域对象中添加了属性:{0},属性值是:{1}"
, scab.getName()
, scab.getValue());
System.out.println(str);
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scab) {
String str = MessageFormat.format(
"ServletContext域对象中删除属性:{0},属性值是:{1}"
, scab.getName()
, scab.getValue());
System.out.println(str);
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scab) {
String str = MessageFormat.format(
"ServletContext域对象中替换了属性:{0}的值"
, scab.getName());
System.out.println(str);
}
}
web.xml中注册此监听器:
<!--注册针对ServletContextAttribute进行监听的监听器-->
<listener>
<description>MyServletContextAttributeListener监听器</description>
<listener-class>com.tp.listeners.MyServletContextAttributeListener</listener-class>
</listener>
index.jsp:
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<body>
<h2>Hello World!</h2>
<%
//往application域对象中添加属性
application.setAttribute("name", "TP");
//替换application域对象中name属性的值
application.setAttribute("name", "Java");
//移除application域对象中name属性
application.removeAttribute("name");
%>
</body>
</html>
启动项目,访问localhost:8080,观察控制台输出:
从运行结果中可以看到,ServletContextListener监听器成功监听到了ServletContext域对象(application)中的属性值的变化情况。
其他两个域对象属性监听器类似。
5.5 HttpSessionBindingListener监听器范例:
实现了HttpSessionBindingListener接口的JavaBean对象可以感知自己被绑定到Session中和 Session中删除的事件
当对象被绑定到HttpSession对象中时,web服务器调用该对象的void valueBound(HttpSessionBindingEvent event)
方法
当对象从HttpSession对象中解除绑定时,web服务器调用该对象的void valueUnbound(HttpSessionBindingEvent event)
方法
使用示例:
package com.tp.models.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
/**
* FileName: Cat
* Author: TP
* Description: 用于测试HttpSessionBindingListener的JavaBean
*/
@Data
@AllArgsConstructor
public class Cat implements HttpSessionBindingListener {
private String name;
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println(String.format("%s被加入到session中了", name));
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println(String.format("%s从session中被移除了", name));
}
}
index.jsp:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@page import="com.tp.models.entity.Cat"%>
<!DOCTYPE HTML>
<html>
<body>
<h2>Hello World!</h2>
<%
//往application域对象中添加属性
application.setAttribute("name", "TP");
//替换application域对象中name属性的值
application.setAttribute("name", "Java");
//移除application域对象中name属性
application.removeAttribute("name");
//将javabean对象绑定到Session中
session.setAttribute("bean",new Cat("中国狸花猫"));
//从Session中删除javabean对象
session.removeAttribute("bean");
%>
</body>
</html>
启动项目,访问localhost:8080,观察控制台如下:
5.6 HttpSessionActivationListener监听器范例:
实现了HttpSessionActivationListener接口的JavaBean对象可以感知自己被活化(反序列化)和钝化(序列化)的事件
- 当绑定到HttpSession对象中的javabean对象将要随HttpSession对象被钝化(序列化)之前,web服务器调用该javabean对象的void sessionWillPassivate(HttpSessionEvent event) 方法。这样javabean对象就可以知道自己将要和HttpSession对象一起被序列化(钝化)到硬盘中.
- 当绑定到HttpSession对象中的javabean对象将要随HttpSession对象被活化(反序列化)之后,web服务器调用该javabean对象的void sessionDidActive(HttpSessionEvent event)方法。这样javabean对象就可以知道自己将要和 HttpSession对象一起被反序列化(活化)回到内存中
活化: javabean对象和Session一起被反序列化(活化)到内存中。
钝化: javabean对象存在Session中,当服务器把session序列化到硬盘上时,如果Session中的javabean对象实现了Serializable接口
如果Session中的javabean对象没有实现Serializable接口,那么服务器会先把Session中没有实现Serializable接口的javabean对象移除,然后再把Session序列化(钝化)到硬盘中
测试JavaBean:
package com.tp.models.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import java.io.Serializable;
/**
* FileName: Bird
* Author: TP
* Description:测试HttpSessionActivationListener用的JavaBean
*/
@Data
@AllArgsConstructor
public class Bird implements HttpSessionActivationListener, Serializable {
private static final long serialVersionUID = 7589841135210272124L;
private String name;
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println(name + "和session一起被序列化(钝化)到硬盘了,session的id是:" + se.getSession().getId());
}
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println(name + "和session一起从硬盘反序列化(活化)回到内存了,session的id是:" + se.getSession().getId());
}
}
为了观察绑定到HttpSession对象中的javabean对象随HttpSession对象一起被钝化到硬盘上和从硬盘上重新活化回到内存中的的过程,我们需要借助tomcat服务器帮助我们完成HttpSession对象的钝化和活化过程(配置tomcat服务器在关闭时将session持久化到本地)。
具体做法如下:
在webapp\META-INF文件夹下创建一个context.xml文件,如下所示:
context.xml中内容如下:
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="/Users/tianpeng/Desktop/session"/>
</Manager>
</Context>
在context.xml文件文件中配置了1分钟之后就将HttpSession对象钝化到本地硬盘的/Users/tianpeng/Desktop/session文件夹中
index.jsp改造如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@page import="com.tp.models.entity.Cat"%>
<%@page import="com.tp.models.entity.Bird"%>
<!DOCTYPE HTML>
<html>
<body>
<h3>一访问JSP页面,HttpSession就创建了,创建好的Session的Id是:${pageContext.session.id}</h3>
<%
//往application域对象中添加属性
application.setAttribute("name", "TP");
//替换application域对象中name属性的值
application.setAttribute("name", "Java");
//移除application域对象中name属性
application.removeAttribute("name");
//将javabean对象绑定到Session中
session.setAttribute("bean",new Cat("中国狸花猫"));
//从Session中删除javabean对象
session.removeAttribute("bean");
//javabean对象绑定到Session中
session.setAttribute("bean",new Bird("百灵鸟"));
%>
</body>
</html>
启动项目,访问localhost:8080:
访问了index.jsp页面,服务器就会马上创建一个HttpSession对象,然后将实现了HttpSessionActivationListener接口的JavaBean对象绑定到session对象中,这个jsp页面在等待1分钟之后没有人再次访问,那么服务器就会自动将这个HttpSession对象钝化(序列化)到硬盘上:
控制台输出:
我们到指定的持久化session的目的文件夹中可以看到如下图所示:
我们将服务器关闭,此时session将失效,重新启动项目,再次访问localhost:8080,会发现服务器又会自动将已经钝化(序列化)到硬盘上HttpSession对象重新活化(反序列化)回到内存中。运行结果如下