本章要点:

  • 理解监听器的作用
  • 掌握监听器的开发与部署
  • 编写在线人数统计

在Web应用中,有时候你可能想要在Web应用程序启动和关闭时来执行一些任务(如数据库连接的建立和释放),或者你想要监控 Session 的创建和销毁,你还希望在ServletContext、HttpSession, 以及ServletRequest 对象中的属性发生改变时得到通知,那么你可以通过Servlet 监听器来实现你的目的。

15.1 监听器接口

Servlet API 中定义了8个监听器接口,可以用于监听 ServletContext、HttpSession 和 ServletRequest 对象的生命周期事件,以及这些对象的属性改变事件。这8个 监听器接口如表15-1 所示。
image.png

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 所示。

  1. package cn.itguigu.listener;
  2. import javax.naming.Context;
  3. import javax.naming.InitialContext;
  4. import javax.naming.NamingException;
  5. import javax.servlet.ServletContext;
  6. import javax.servlet.ServletContextEvent;
  7. import javax.servlet.ServletContextListener;
  8. import javax.sql.DataSource;
  9. /**
  10. * @PackageName:cn.itguigu.listener
  11. * @ClassName:MyServletContextListener
  12. * @Description:
  13. * @Author:lyg
  14. * @Date:2021/12/28 10:47
  15. */
  16. public class MyServletContextListener implements ServletContextListener {
  17. @Override
  18. public void contextInitialized(ServletContextEvent sce) {
  19. ServletContext sc = sce.getServletContext();
  20. // 得到 Servlet 上下文参数
  21. String jndi = sc.getInitParameter("jndi");
  22. Context ctx;
  23. try {
  24. ctx = new InitialContext();
  25. DataSource ds = (DataSource) ctx.lookup(jndi);
  26. // 将DataSource对象保存为ServletContext的属性
  27. sc.setAttribute("dataSource", ds);
  28. } catch (NamingException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. @Override
  33. public void contextDestroyed(ServletContextEvent sce) {
  34. }
  35. }

在Servlet 中可以 使用如例 15-2所示的代码来访问 ServletContext 中的DataSource 对象,进而获取数据库连接,访问数据库。
例 15-2 在Servlet 中访问ServletContext中的DataSource 对象

  1. DataSource ds = (DataSource) sc.getAttribute("dataSource");
  2. try {
  3. Connection conn = ds.getConnection();
  4. Statement stmt = conn.createStatement();
  5. ResultSet resultSet = stmt.executeQuery("....");
  6. } catch (SQLException throwables) {
  7. throwables.printStackTrace();
  8. }

要让 Web 容器在 Web应用程序启动时通知 MyServletContextListener, 你需要在web.xml 文件中使用元素来配置监听器类。对于本例

  1. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  4. http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  5. version="3.1">
  6. <display-name>liuyaguang Archetype Created Web Application web.xml</display-name>
  7. <context-param>
  8. <param-name>jndi</param-name>
  9. <param-value>java:comp/env/jdbc/bookstore</param-value>
  10. </context-param>
  11. <listener>
  12. <listener-class>cn.itguigu.listener.MyServletContextListener</listener-class>
  13. </listener>
  14. </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 所示的内容。

  1. <Context docBase="F:\JSPLesson\ch15" reloadable="true" />

step 2 : 编写login.html

将编写好的login.html文件放到 F:\xxxx\xxx 目录下 。完整的代码编写如例:15-5 所示

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <form action="onlineUserServlet" method="post">
  9. <table>
  10. <tr>
  11. <td>请输入用户名</td>
  12. <td><input type="text" name="user"></td>
  13. </tr>
  14. <tr>
  15. <td>请输入密码</td>
  16. <td><input type="password" name="password"></td>
  17. </tr>
  18. <tr>
  19. <td><input type="reset" value="重置"></td>
  20. <td><input type="submit" value="登录"></td>
  21. </tr>
  22. </table>
  23. </form>
  24. </body>
  25. </html>

step 3 : 编写UserList.java , User.java, OnlineUserServlet.java 和 LogoutServlet.java

  1. public class UserList {
  2. private static final UserList userList = new UserList();
  3. private Vector<String> v;
  4. private UserList(){
  5. v = new Vector<>();
  6. }
  7. public static UserList getInstance() {
  8. return userList;
  9. }
  10. public void addUser(String name) {
  11. if (name != null) {
  12. v.addElement(name);
  13. }
  14. }
  15. public void removeUser(String name) {
  16. if (name != null) {
  17. v.remove(name);
  18. }
  19. }
  20. public Enumeration<String> getUserList(){
  21. return v.elements();
  22. }
  23. public int getUserCount() {
  24. return v.size();
  25. }
  26. }
  1. public class User implements HttpSessionBindingListener {
  2. private String name;
  3. private UserList ul = UserList.getInstance();
  4. public User() {}
  5. public User(String name) {
  6. this.name = name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. @Override
  15. public void valueBound(HttpSessionBindingEvent event) {
  16. System.out.println("将用户: "+ name +"添加到session对象中" + "HttpSessionBindingEvent" + event.toString());
  17. ul.addUser(name);
  18. }
  19. @Override
  20. public void valueUnbound(HttpSessionBindingEvent event) {
  21. System.out.println("将用户: "+ name +"添加到session对象中" + "HttpSessionBindingEvent" + event.toString());
  22. ul.removeUser(name);
  23. }
  24. }
  1. public class OnlineUserServlet extends HttpServlet {
  2. @Override
  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp)
  4. throws ServletException, IOException {
  5. req.setCharacterEncoding("gb2312");
  6. String name = req.getParameter("user");
  7. String pwd = req.getParameter("password");
  8. if (name == null || pwd == null || name.equals("") || pwd.equals("")) {
  9. resp.sendRedirect(req.getContextPath() + "/itguigu/login.html");
  10. } else {
  11. HttpSession session = req.getSession();
  12. User user = (User)session.getAttribute("user");
  13. if (null == user || !name.equals(user.getName())) {
  14. user = new User(name);
  15. session.setAttribute("user", user);
  16. }
  17. resp.setContentType("text/html;charset=gb2312");
  18. PrintWriter out = resp.getWriter();
  19. out.println("欢迎用户<b> " + name +"</b> 登录");
  20. final UserList ul = UserList.getInstance();
  21. out.println("<br> 当前在线的用户列表: <br>");
  22. final Enumeration<String> enums = ul.getUserList();
  23. int i =0 ;
  24. while (enums.hasMoreElements()) {
  25. out.println(enums.nextElement());
  26. out.println("&nbsp;&nbsp;&nbsp;&nbsp;");
  27. if (++i == 10) {
  28. out.println("<br>");
  29. }
  30. }
  31. out.println("<br> 当前在线的用户数:" + i);
  32. out.println("<p><a href=" + req.getContextPath() + "/itguigu/logout> 退出登录");
  33. out.close();
  34. }
  35. }
  36. @Override
  37. protected void doPost(HttpServletRequest req, HttpServletResponse resp)
  38. throws ServletException, IOException {
  39. doGet(req, resp);
  40. }
  41. }
  1. public class LogoutServlet extends HttpServlet {
  2. @Override
  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp)
  4. throws ServletException, IOException {
  5. resp.setContentType("text/html;charset=gb2312");
  6. HttpSession session = req.getSession();
  7. User user = (User) session.getAttribute("user");
  8. session.invalidate();
  9. PrintWriter out = resp.getWriter();
  10. out.println("<html><head><title> 退出登录</title></head><body>");
  11. out.println(user.getName() + ",你已退出登录<br>");
  12. out.println("<a href="+ req.getContextPath() +"/itguigu/login.html");
  13. out.close();
  14. }
  15. @Override
  16. protected void doPost(HttpServletRequest req, HttpServletResponse resp)
  17. throws ServletException, IOException {
  18. doGet(req, resp);
  19. }
  20. }

step 4 : 编译 上述4 个Java 源文件

step 5: 部署 Servlet

step 6 : 运行在线人数统计程序

IMG_20211228_223142.jpg

一、Listener (监听器)

image.png

二 、分类

image.png

2.1、监听域对象的创建 和 销毁

image.png

2.1.1、ServletContextListener (ServletContext 域对象 监听器)

image.png

2.1.2、HttpSessionListener (HttpSession 域对象 监听器)

image.png

2.1.3 、ServletRequestListener (ServletRequest 域对象 监听器)

image.png
image.png
image.png

2.2 、域对象中属性的变更的事件监听

image.png
image.png

2.3、 感知Session 绑定的事件监听器

image.png
image.png
image.png

2.4、HttpSessionActivationListener 接口 感知 JavaBean 的活化 和 钝化

image.png

image.png