本章要点:
- 理解监听器的作用
- 掌握监听器的开发与部署
- 编写在线人数统计
在Web应用中,有时候你可能想要在Web应用程序启动和关闭时来执行一些任务(如数据库连接的建立和释放),或者你想要监控 Session 的创建和销毁,你还希望在ServletContext、HttpSession, 以及ServletRequest 对象中的属性发生改变时得到通知,那么你可以通过Servlet 监听器来实现你的目的。
15.1 监听器接口
Servlet API 中定义了8个监听器接口,可以用于监听 ServletContext、HttpSession 和 ServletRequest 对象的生命周期事件,以及这些对象的属性改变事件。这8个 监听器接口如表15-1 所示。
15.1.1 监听器接口 列表
监听器接口 | 方法 | 说明 |
---|---|---|
ServletContextListener | ||
javax.servlet.ServletContextListener | contextDestroyed contextInitialized |
如果想要在Servlet上下文对象(ServletContext)初始化时或者将要被销毁时得到通知,可以实现这个接口。实现该接口的类必须在Web应用程序的部署描述符中进行配置 |
javax.servlet.ServletContext.AttributeListener | attributeAdded attributeRemoved attributeReplaced |
如果想在Servlet上下文中的属性列表发生改变时得到通知,可以实现这个接口。实现该接口的类必须在Web应用程序的部署描述符中进行配置 |
HttpSessionListener | ||
java.servlet.http.HttpSessionListener | sessionCreated sessionDestroyed |
如果想要在Session创建后 或者在Session无效前得到通知,可以实现这个接口。实现该接口的类必须在Web应用程序的部署描述符中进行配置 |
javax.servlet.htttp.HttpSessionActivationListener | sessionDidActivate sessionWillPassivate |
实现这个接口的对象,如果绑定到Session中,当Session被钝化或激活时,Servlet容器将通知该对象 |
javax.servlet.htttp.HttpSessionBindingListener | valueBound valueUnbound |
如果想让一个对象在绑定到Session中或者从Session中被删除时得到通知,那么可以让这个对象实现该接口 |
javax.servlet.http.HttpSessionAttributeListener | attributeAdded attributeRemoved attributeReplaced |
如果想要在Session中的属性列表发生改变时得到通知,可以实现这个接口 |
ServletRequestListener | ||
javax.servlet.ServletRequestListener | requestDestroyed requestInitialized |
如果想要在请求对象初始化时或者将要被销毁时得到通知,可以实现这个接口 |
javax.servlet.ServletRequestAttributeListener | attributeAdded attributeRemoved attributeReplaced |
如果想要在Servlet 请求对象中的属性发生改变时得到通知,可以实现这个接口 |
HttpSessionAttributeListener 和 HttpSessionBindingListener 接口的主要区别是:前者用于监听Session中何时添加、删除、或者替换了某种类型的属性,而后者是由属性自身来实现,以便属性知道它何时添加到一个Session中,或者何时从Session中被删除。
eg: HttpSessionBindingListener :比如往session中添加一个 User 对象,那就让 User 对象类实现该接口,当User对象从session中被删除时会回调通知该类方法valueUnbound(); — 指的是 属性对象本身
HttpSessionAttributeListener : 是往 session 对象的属性操作时,监听通知该类方法; — 指的是 session对象属性的变化,不是针对属性(任意属性都可以);
15.2 ServletContextListener 接口
有时候我们可能希望在Web 应用程序启动时执行一些初始化的任务,那么我们可以编写一个实现了ServletContextListener 接口的监听器类。
ServletContextListener 接口提供了下面的方法:
-> public void contextInitialized(ServletContextEvent sce)
当Web应用程序初始化进程正开始时,Web容器调用这个方法。该方法将在所有的过滤器和Servlet初始化之前被调用。
-> public void contextDestroyed(ServletContextEvent sce)
当Servlet 上下文将要被关闭时,Web容器调用这个方法。该方法将在所有的Servlet和过滤器销毁之后被调用。
Web容器通过 ServletContextEvent 对象来通知实现了 ServletContextListener 接口的监听器,监听器可以利用ServletContextEvent对象来得到 ServletContext对象。
javax.servlet.ServletContextEvent类除了构造方法外,只定义了一个方法,如下所示:
-> public ServletContext getServletContext()
该方法用于得到 ServletContext对象。
我们看一个实现了ServletContextListener 接口的监听器例子。我们在Web应用程序启动时初始化DataSource 对象,然后将它放到ServletContext中,Servlet 从 ServletContext中访问 DataSource对象,进而得到数据库连接,访问数据库。代码如例 15-1 所示。
package cn.itguigu.listener;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.sql.DataSource;
/**
* @PackageName:cn.itguigu.listener
* @ClassName:MyServletContextListener
* @Description:
* @Author:lyg
* @Date:2021/12/28 10:47
*/
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext sc = sce.getServletContext();
// 得到 Servlet 上下文参数
String jndi = sc.getInitParameter("jndi");
Context ctx;
try {
ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(jndi);
// 将DataSource对象保存为ServletContext的属性
sc.setAttribute("dataSource", ds);
} catch (NamingException e) {
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
在Servlet 中可以 使用如例 15-2所示的代码来访问 ServletContext 中的DataSource 对象,进而获取数据库连接,访问数据库。
例 15-2 在Servlet 中访问ServletContext中的DataSource 对象
DataSource ds = (DataSource) sc.getAttribute("dataSource");
try {
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet resultSet = stmt.executeQuery("....");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
要让 Web 容器在 Web应用程序启动时通知 MyServletContextListener, 你需要在web.xml 文件中使用
<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_3_1.xsd"
version="3.1">
<display-name>liuyaguang Archetype Created Web Application web.xml</display-name>
<context-param>
<param-name>jndi</param-name>
<param-value>java:comp/env/jdbc/bookstore</param-value>
</context-param>
<listener>
<listener-class>cn.itguigu.listener.MyServletContextListener</listener-class>
</listener>
</web-app>
在配置监听器时,不需要告诉容器你实现了什么监听器接口,容器自己会发现。
15.3 HttpSessionBindingListener 接口
如果一个对象实现了 HttpSessionBindingListener接口,当这个对象被绑定到Session 中或者从Session中被删除时,Servlet 容器会通知这个对象,而这个对象在接收到通知后,可以做一些初始化或清除状态的操作。
例如,在网络购物应用中,可以让购物车对象实现HttpSessionBindingListener接口,当顾客选购商品时,Web应用程序创建购物车对象,保存在Session中,并在购物车中放入预选的商品。当顾客没有结账就离开了站点,或者顾客在浏览商品介绍的时候,Session超时,这个时候,Servlet容器就会通知购物车对象,它要从Session中被删除了。购物车对象在得到通知后,可以把顾客选购的商品信息保存到数据库中。当顾客再次来到网站购物的时候,Web应用程序再将购物车对象保存(绑定)到Session中时,Servlet容器会通知购物车对象,此时,购物车对象可以从数据库中加载先前保存的商品信息。顾客会惊奇地发现,以前预购的商品信息仍然存在。
javax.servlet.http.HttpSessionBindingListener接口提供了下面的方法:
-> public void valueBound(HttpSessionBindingEvent event)
当对象正在被绑定到Session 中,Serlvet容器调用这个方法来通知该对象。
-> public void valueUnbound(HttpSessionBindingEvent event)
当 从Session 中删除对象时,Servlet容器调用这个方法来通知该对象。
servlet 容器 通过HttpSessionBindingEvent对象来通知实现了HttpSessionBindingListener接口的对象,而该对象可以利用 HttpSessionBindingEvent 对象来访问与它相联系的HttpSession对象。javax.servlet.http.HttpSessionBindingEvent 类提供了以下两种方法:
-> public HttpSessionBindingEvent(HttpSession session, java.lang.String name)
-> public HttpSessionBindingEvent(HttpSession session,java.lang.String name, java.lang.Object value)
上面两个构造方法构造一个事件对象,当一个对象被绑定到 Session 中或者从Session 中被删除时,用这个事件对象来通知它。
-> public java.lang.String getName()
返回绑定到Session 中或者从Session中删除的属性的名字。
-> public java.lang.Object getValue()
返回 被添加、删除、替换的属性值。如果属性被替换,这个方法返回属性先前的值。
-> public HttpSession getSession()
返回HttpSession 对象。
15.4 在线人数统计程序
下面,我们利用HttpSessionBingdingListener 接口,编写一个在线人数统计的程序。当一个用户登录后,显示欢迎信息,同时显示出当前在线的总人数和 用户名单。当一个用户退出登录或者Session 超时值发生时,从在线用户名单中删除这个用户,同时将在线总人数减 1. 这个功能的完成,主要是利用一个实现了 HttpSessionBindingListener 接口的对象,当这个对象被绑定到Session中或者从 Session中被删除时,更新当前在线的用户名单。
step 1 : 配置web应用程序的运行目录
在%CATALINA_HOME%\conf\Catalina\localhost\目录下新建ch15.xml文件,输入如例15-4 所示的内容。
<Context docBase="F:\JSPLesson\ch15" reloadable="true" />
step 2 : 编写login.html
将编写好的login.html文件放到 F:\xxxx\xxx 目录下 。完整的代码编写如例:15-5 所示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="onlineUserServlet" method="post">
<table>
<tr>
<td>请输入用户名</td>
<td><input type="text" name="user"></td>
</tr>
<tr>
<td>请输入密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td><input type="reset" value="重置"></td>
<td><input type="submit" value="登录"></td>
</tr>
</table>
</form>
</body>
</html>
step 3 : 编写UserList.java , User.java, OnlineUserServlet.java 和 LogoutServlet.java
public class UserList {
private static final UserList userList = new UserList();
private Vector<String> v;
private UserList(){
v = new Vector<>();
}
public static UserList getInstance() {
return userList;
}
public void addUser(String name) {
if (name != null) {
v.addElement(name);
}
}
public void removeUser(String name) {
if (name != null) {
v.remove(name);
}
}
public Enumeration<String> getUserList(){
return v.elements();
}
public int getUserCount() {
return v.size();
}
}
public class User implements HttpSessionBindingListener {
private String name;
private UserList ul = UserList.getInstance();
public User() {}
public User(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("将用户: "+ name +"添加到session对象中" + "HttpSessionBindingEvent" + event.toString());
ul.addUser(name);
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("将用户: "+ name +"添加到session对象中" + "HttpSessionBindingEvent" + event.toString());
ul.removeUser(name);
}
}
public class OnlineUserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("gb2312");
String name = req.getParameter("user");
String pwd = req.getParameter("password");
if (name == null || pwd == null || name.equals("") || pwd.equals("")) {
resp.sendRedirect(req.getContextPath() + "/itguigu/login.html");
} else {
HttpSession session = req.getSession();
User user = (User)session.getAttribute("user");
if (null == user || !name.equals(user.getName())) {
user = new User(name);
session.setAttribute("user", user);
}
resp.setContentType("text/html;charset=gb2312");
PrintWriter out = resp.getWriter();
out.println("欢迎用户<b> " + name +"</b> 登录");
final UserList ul = UserList.getInstance();
out.println("<br> 当前在线的用户列表: <br>");
final Enumeration<String> enums = ul.getUserList();
int i =0 ;
while (enums.hasMoreElements()) {
out.println(enums.nextElement());
out.println(" ");
if (++i == 10) {
out.println("<br>");
}
}
out.println("<br> 当前在线的用户数:" + i);
out.println("<p><a href=" + req.getContextPath() + "/itguigu/logout> 退出登录");
out.close();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=gb2312");
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
session.invalidate();
PrintWriter out = resp.getWriter();
out.println("<html><head><title> 退出登录</title></head><body>");
out.println(user.getName() + ",你已退出登录<br>");
out.println("<a href="+ req.getContextPath() +"/itguigu/login.html");
out.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}