Servlet - Listeners
1. What
监听器 Listener 是一个实现特定接口的 Java 程序,这个程序专门用于监听另一个 Java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即自动执行。
监听器的相关概念
- 事件(Events ):方法调用、属性改变、状态改变等。
- 事件源(Event source):被监听的对象
- 监听器(Listener ):用于监听事件源对象 ,事件源对象状态的变化都会触发监听器。
- 注册监听器(Register listener):将监听器与事件源进行绑定。
Servlet 规范中定义了 8 个监听器接口,可以用于监听 ServletContext、HttpSession 和 ServletRequest 对象的生命周期和属性变化事件。开发 Servlet 监听器需要实现相应的监听器接口并重写接口中的方法。
监听器 Listener 按照监听的事件划分,可以分为 3 类
- Listeners for object creation and destruction(监听对象创建和销毁的监听器)
- Listeners that listens for property changes in an object(监听对象中属性变更的监听器)
- Listeners that listens for changes in the state of objects in HttpSession(监听 HttpSession 中的对象状态改变的监听器)
监听对象创建和销毁的监听器
Servlet 规范定义了监听 ServletContext、HttpSession、HttpServletRequest 这三个对象创建和销毁事件的监听器,如下表所示。
event source (事件源) | listener(监听器) | description(监听器描述) | create or destory method(创建和销毁方法) |
---|---|---|---|
ServletContext | ServletContextListener | 用于监听 ServletContext 对象的创建过程 | void contextInitialized (ServletContextEvent sce) |
ServletContext | ServletContextListener | 用于监听 ServletContext 对象的销毁过程 | void contextDestroyed (ServletContextEvent sce) |
HttpSession | HttpSessionListener | 用于监听 HttpSession 对象的创建过程 | void sessionCreated (HttpSessionEvent se) |
HttpSession | HttpSessionListener | 用于监听 HttpSession 对象的销毁过程 | void sessionDestroyed (HttpSessionEvent se) |
ServletRequest | ServletRequestListener | 用于监听 ServletRequest 对象的创建过程 | void requestInitialized (ServletRequestEvent sre) |
ServletRequest | ServletRequestListener | 用于监听 ServletRequest 对象的销毁过程 | void requestDestroyed (ServletRequestEvent sre) |
监听对象中属性变更的监听器
Servlet 规范定义了监听 ServletContext、HttpSession、HttpServletRequest 这三个对象中的属性变更事件的监听器,这三个监听器接口分别是 ServletContextAttributeListener、HttpSessionAttributeListener 和 ServletRequestAttributeListener。这三个接口中都定义了三个方法,用来处理被监听对象中属性的增加,删除和替换事件。同一种事件在这三个接口中对应的方法名称完全相同,只是参数类型不同,如下表所示。
event source (事件源) | listener(监听器) | description(监听器描述) | method(方法) |
---|---|---|---|
ServletContext | ServletContextAttributeListener | 用于监听 ServletContext 对象的属性新增 | public void attributeAdded (ServletContextAttributeEvent scae) |
ServletContext | ServletContextAttributeListener | 用于监听 ServletContext 对象的属性移除 | public void attributeRemoved (ServletContextAttributeEvent scae) |
ServletContext | ServletContextAttributeListener | 用于监听 ServletContext 对象的属性替换 | public void attributeReplaced (ServletContextAttributeEvent scae) |
HttpSession | HttpSessionAttributeListener | 用于监听 ServletContext 对象的属性新增 | public void attributeAdded (HttpSessionBindingEvent hsbe) |
HttpSession | HttpSessionAttributeListener | 用于监听 ServletContext 对象的属性移除 | public void attributeRemoved (HttpSessionBindingEvent hsbe) |
HttpSession | HttpSessionAttributeListener | 用于监听 ServletContext 对象的属性替换 | public void attributeReplaced (HttpSessionBindingEvent hsbe) |
HttpServletRequest | ServletRequestAttributeListener | 用于监听 ServletContext 对象的属性新增 | public void attributeAdded (ServletRequestAttributeEvent srae) |
HttpServletRequest | ServletRequestAttributeListener | 用于监听 ServletContext 对象的属性移除 | public void attributeRemoved (ServletRequestAttributeEvent srae) |
HttpServletRequest | ServletRequestAttributeListener | 用于监听 ServletContext 对象的属性替换 | public void attributeReplaced (ServletRequestAttributeEvent srae) |
监听 HttpSession 中的对象状态改变的监听器
Session 中的对象可以有多种状态:绑定到 Session 中、从 Session 中解除绑定、随 Session 对象持久化到存储设备中(钝化)、随 Session 对象从存储设备中恢复(活化)。
Servlet 规范中定义了两个特殊的监听器接口,用来帮助对象了解自己在 Session 中的状态:HttpSessionBindingListener 接口和 HttpSessionActivationListener 接口 ,实现这两个接口的类不需要进行注册。
event source (事件源) | listener(监听器) | description(监听器描述) | method(方法) |
---|---|---|---|
HttpSession | HttpSessionBindingListener | 用于监听 JavaBean 对象绑定到 HttpSession 对象的事件 | void valueBound (HttpSessionBindingEvent event) |
HttpSession | HttpSessionBindingListener | 用于监听 JavaBean 对象从 HttpSession 对象解绑的事件 | void valueUnbound (HttpSessionBindingEvent event) |
HttpSession | HttpSessionActivationListener | 用于监听 HttpSession 中对象活化的过程 | void sessionWillPassivate (HttpSessionBindingEvent event) |
HttpSession | HttpSessionActivationListener | 用于监听 HttpSession 中对象钝化的过程 | void sessionDidActive (HttpSessionBindingEvent event) |
2. Why
- Listener 我是这样理解他的,他是一种观察者模式的实现:我们在 web.xml 中配置 listener 的时候就是把一个被观察者放入的观察者的观察对象队列中,当被观察者触发了注册事件时观察者作出相应的反应。在 jsp/servlet 中具体的实现是在 web.xml 中注册 Listener ,由 Container 在特定事件发生时呼叫特定的实现 Listener 的类。
- Listener mode VS observer mode:
3. How
Servlet 监听器Listener 的执行顺序
Listener的启动优先级是大于过滤器的,即Listener>Filter>Servlet,如果有很多Listener类,那么Listener的作用时机是根据你在web.xml中注册的顺序来决定的,即按照从上往下的顺序来加载.
1.在web.xml中配置
2. 在 ServletContext 创建之前,Listener监听器(包括其他类型监听器)会先按配置顺序初始化;
3. 然后 ServletContext 初始化完成后会按照监听器配置的顺序回调相应的方法,比如 ServletContextListener 的 contextInitialized() 方法。
使用场景
- 在系统启动时加载初始化信息
- 统计网站的访问量
- 统计在线人数和在线用户
- 运用在一些框架(如Spring)中完成特定功能.
4. Sample
Servlet Listener config
我们可以使用@WebListener注解来声明一个类作为Listener,但是该类应该实现一个或多个Listener接口。
我们可以在web.xml中定义listener:
<listener>
<listener-class>
com.journaldev.listener.AppContextListener
</listener-class>
</listener>
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_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>ServletListenerExample</display-name>
<context-param>
<param-name>DBUSER</param-name>
<param-value>pankaj</param-value>
</context-param>
<context-param>
<param-name>DBPWD</param-name>
<param-value>password</param-value>
</context-param>
<context-param>
<param-name>DBURL</param-name>
<param-value>jdbc:mysql://localhost/mysql_db</param-value>
</context-param>
<listener>
<listener-class>com.journaldev.listener.AppContextListener</listener-class>
</listener>
<listener>
<listener-class>com.journaldev.listener.AppContextAttributeListener</listener-class>
</listener>
<listener>
<listener-class>com.journaldev.listener.MySessionListener</listener-class>
</listener>
<listener>
<listener-class>com.journaldev.listener.MyServletRequestListener</listener-class>
</listener>
</web-app>
DBConnectionManager:这是数据库连接的类,为了简单起见,我没有为实际的数据库连接提供代码。将这个对象设置为servlet上下文的属性。
package com.journaldev.db;
import java.sql.Connection;
public class DBConnectionManager {
private String dbURL;
private String user;
private String password;
private Connection con;
public DBConnectionManager(String url, String u, String p){
this.dbURL=url;
this.user=u;
this.password=p;
//create db connection now
}
public Connection getConnection(){
return this.con;
}
public void closeConnection(){
//close DB connection here
}
}
MyServlet:一个简单的servlet类,将使用会话,属性等。
package com.journaldev.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext ctx = request.getServletContext();
ctx.setAttribute("User", "Pankaj");
String user = (String) ctx.getAttribute("User");
ctx.removeAttribute("User");
HttpSession session = request.getSession();
session.invalidate();
PrintWriter out = response.getWriter();
out.write("Hi "+user);
}
}
接下来将实现监听器类,为常用的监听器提供了示例侦听器类 —— ServletContextListener,ServletContextAttributeListener,ServletRequestListener和HttpSessionListener。
ServletContextListener:将读取servlet context init参数来创建DBConnectionManager对象,并将其设置为ServletContext对象的属性。
package com.journaldev.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import com.journaldev.db.DBConnectionManager;
@WebListener
public class AppContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext ctx = servletContextEvent.getServletContext();
String url = ctx.getInitParameter("DBURL");
String u = ctx.getInitParameter("DBUSER");
String p = ctx.getInitParameter("DBPWD");
//create database connection from init parameters and set it to context
DBConnectionManager dbManager = new DBConnectionManager(url, u, p);
ctx.setAttribute("DBManager", dbManager);
System.out.println("Database connection initialized for Application.");
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ServletContext ctx = servletContextEvent.getServletContext();
DBConnectionManager dbManager = (DBConnectionManager) ctx.getAttribute("DBManager");
dbManager.closeConnection();
System.out.println("Database connection closed for Application.");
}
}
ServletContextAttributeListener:在servlet上下文中添加,删除或替换属性时,记录事件的简单实现。
package com.journaldev.listener;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class AppContextAttributeListener implements ServletContextAttributeListener {
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext attribute added::{"+servletContextAttributeEvent.getName()+","+servletContextAttributeEvent.getValue()+"}");
}
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext attribute replaced::{"+servletContextAttributeEvent.getName()+","+servletContextAttributeEvent.getValue()+"}");
}
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext attribute removed::{"+servletContextAttributeEvent.getName()+","+servletContextAttributeEvent.getValue()+"}");
}
}
HttpSessionListener:创建或销毁会话时记录事件的简单实现。
package com.journaldev.listener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class MySessionListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent sessionEvent) {
System.out.println("Session Created:: ID="+sessionEvent.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent sessionEvent) {
System.out.println("Session Destroyed:: ID="+sessionEvent.getSession().getId());
}
}
ServletRequestListener:接口的简单实现,用于在请求初始化和销毁时记录ServletRequest IP地址。
package com.journaldev.listener;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
ServletRequest servletRequest = servletRequestEvent.getServletRequest();
System.out.println("ServletRequest destroyed. Remote IP="+servletRequest.getRemoteAddr());
}
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
ServletRequest servletRequest = servletRequestEvent.getServletRequest();
System.out.println("ServletRequest initialized. Remote IP="+servletRequest.getRemoteAddr());
}
}
现在,将使用URL部署我们的应用程序并在浏览器中访问MyServlet时http://localhost:8080/ServletListenerExample/MyServlet,则将在服务器日志文件中看到以下日志。
ServletContext attribute added::{DBManager,com.journaldev.db.DBConnectionManager@4def3d1b}
Database connection initialized for Application.
ServletContext attribute added::{org.apache.jasper.compiler.TldLocationsCache,org.apache.jasper.compiler.TldLocationsCache@1594df96}
ServletRequest initialized. Remote IP=0:0:0:0:0:0:0:1%0
ServletContext attribute added::{User,Pankaj}
ServletContext attribute removed::{User,Pankaj}
Session Created:: ID=8805E7AE4CCCF98AFD60142A6B300CD6
Session Destroyed:: ID=8805E7AE4CCCF98AFD60142A6B300CD6
ServletRequest destroyed. Remote IP=0:0:0:0:0:0:0:1%0
ServletRequest initialized. Remote IP=0:0:0:0:0:0:0:1%0
ServletContext attribute added::{User,Pankaj}
ServletContext attribute removed::{User,Pankaj}
Session Created:: ID=88A7A1388AB96F611840886012A4475F
Session Destroyed:: ID=88A7A1388AB96F611840886012A4475F
ServletRequest destroyed. Remote IP=0:0:0:0:0:0:0:1%0
Database connection closed for Application.
注意日志的顺序,它按照执行的顺序。当您关闭应用程序或关闭容器时,将显示最后一个日志。