学科分类
目录
Java Web

HttpSessionActivationListener接口

当一个会话开始时,Servlet容器会为会话创建一个HttpSession对象。Servlet容器在某些特殊情况下会把这些HttpSession对象从内存中转移至硬盘,这个过程称为持久化(钝化)。在持久化会话时,Servlet容器不仅会持久化HttpSession对象,还会对它所有可以序列化的属性进行持久化,从而确保存放在会话范围内的共享数据不会丢失。所谓可以序列化的属性就是指该属性所在的类实现了Serializable接口。当会话从持久化的状态变为运行状态的过程被称为活化(或称为加载),一般情况下当服务器重新启动或者单个Web应用启动时,处于会话中的客户端向Web应用发出Http请求时,相应的会话会被激活。

为了监听HttpSession中对象活化和钝化的过程,Servlet API专门提供了HttpSessionActivationListener接口,该接口定义了两个事件处理方法,分别是sessionWillPassivate()方法和sessionDidActivate()方法,接下来针对这两个方法进行讲解。

1、sessionWillPassivate()方法

sessionWillPassivate()方法的完整语法定义如下:

public void sessionWillPassivate(HttpSessionEvent se) 

当绑定到HttpSession对象中的对象将要随HttpSession对象被钝化之前,Web容器将调用这个方法并传递一个HttpSessionEvent类型的事件对象,程序通过这个事件对象可以获得当前被钝化的HttpSession对象。

2、sessionDidActivate()方法

sessionDidActivate()方法的完整语法定义如下:

public void sessionDidActivate(HttpSessionEvent se)

当绑定到HttpSession对象中的对象将要随HttpSession对象被活化之后,Web容器将调用这个方法并传递一个HttpSessionEvent类型的事件对象。

通过前面的讲解,初学者已经对HttpSessionActivationListener这个监听器接口有了一定的了解,接下来分步骤讲解JavaBean对象如何感知与Session绑定的有关事件。

(1)在完成会话的持久化时,会用到会话管理器PersistentManger,它的作用是当Web服务器终止或者单个Web应用被终止时,会对被终止的Web应用的HttpSession对象进行持久化,通过查看Tomcat帮助文档可以发现,PersistentManger持久化会话管理器是在context.xml文件中的<Context>元素中配置的。在<Context>元素中增加一个<Manager>子元素便可以完成Session对象的持久化管理。

打开<Tomcat安装目录>\conf\context.xml文件,在< Context>元素中增加如下信息:

<Manager lassName="org.apache.catalina.session.PersistentManager" 

maxIdleSwap="1">

<Store className="org.apache.catalina.session.FileStore"

directory="itcast"/>

</Manager>

上述代码中,有几个重要的部分,具体如下:

● <Manager>元素专门用于配置会话管理器,它的className属性用于指定负责创建、销毁和持久化Session对象的类。

● maxIdleSwap属性用于指定Session被钝化前的空闲时间间隔(单位为秒),本程序中将时间设置为1秒,如果超过这个时间,管理Session对象的类将把Session对象持久化到存储设备中。

● <Store>元素用于负责完成具体的持久化任务的类。

● directory属性指定保存持久化文件的目录,如果采用相对目录,它是相对于Web应用的工作目录而言的,也就是<Tomcat安装目录>/work/Catalina/localhost/chapter05/itcast。

(2)在chapter05工程的cn.itcast.chapter05.listener包中,编写一个MyBean2.java类,该类实现了HttpSessionActivationListener接口,并实现这个接口中的所有方法,具体如例1所示。

例1 MyBean2.java

 1  package cn.itcast.chapter05.listener;

 2  import javax.servlet.http.HttpSessionActivationListener;

 3  import javax.servlet.http.HttpSessionEvent;

 4  public class MyBean2 implements HttpSessionActivationListener{

 5    private String name;

 6    private int age;

 7    public String getName() {

 8      return name;

 9    }

 10   public void setName(String name) {

 11     this.name = name;

 12   }

 13   public int getAge() {

 14     return age;

 15   }

 16   public void setAge(int age) {

 17     this.age = age;

 18   }

 19   public void sessionDidActivate(HttpSessionEvent arg0) {

 20     System.out.println("MyBean2的对象活化了...");

 21   }

 22   public void sessionWillPassivate(HttpSessionEvent arg0) {

 23     System.out.println("MyBean2的对象钝化了...");

 24   }

 25 }

(3)在chapter05工程的WebContent根目录中,编写一个write.jsp页面,在这个页面中将MyBean2对象保存到Session对象中,以查看MyBean2对象感知自己的Session绑定事件,如例2所示。

例2 write.jsp

 1  <%@ page language="java" contentType="text/html; charset=utf-8"

 2  pageEncoding="utf-8" import="cn.itcast.chapter05.listener.*"%>

 3  <html>

 4  <head>

 5  <title>Insert title here</title>

 6  </head>

 7  <body>

 8    <h1>向Session域中存入数据</h1>

 9    <%

 10     MyBean2 myBean = new MyBean2();

 11     myBean.setName("Tom");

 12     myBean.setAge(20);

 13     session.setAttribute("myBean", myBean);

 14   %>

 15 </body>

 16 </html>

(4)在chapter05工程的WebContent根目录中,编写一个read.jsp页面,在这个页面中读取Session域中的对象,如例3所示。

例3 read.jsp

 1  <%@ page language="java" contentType="text/html; 

 2  charset=utf-8" pageEncoding="utf-8"

 3  import="cn.itcast.chapter05.listener.*"%>

 4  <html>

 5  <head>

 6  <title>Insert title here</title>

 7  </head>

 8  <body>

 9    <h1>从Session域中读取数据</h1>

 10   姓名:${sessionScope.myBean.name } 

 11   年龄:${sessionScope.myBean.age }

 12 </body>

 13 </html>

(5)启动Web服务器,打开IE浏览器在地址栏中输入http://localhost:8080/chapter05/write.jsp,访问write.jsp页面,此时,控制台窗口中显示的结果如图1所示。

图1 write.jsp

从图1可以看出,已经将Session域中存入了JavaBean对象,接下来在浏览器中访问read.jsp页面,如图2所示。

图2 read.jsp

从图2可以看出,在read.jsp页面中已经获取了Session域中存入的JavaBean对象,稍后会发现控制台窗口显示MyBean2对象钝化了,如图3所示。

图3 控制台窗口

从图3可以看出,MyBean2对象钝化了,该对象会被保存在硬盘上,如果此时想使用MyBean2对象,再次访问read.jsp页面时,如图4所示。

图4 read.jsp

从图4可以看出,使用浏览器再次访问read.jsp页面时,并没有显示MyBean2对象的姓名和年龄,这是因为MyBean2类没有实现Serializable接口,那么当Servlet容器持久化一个HttpSession对象时,不会持久化存放在其中的MyBean2对象,当HttpSession对象被重新加载到内存中后,它的MyBean2对象信息会丢失,因此无法获取到用户的信息。

接下来对例3进行修改,让其实现Serializable接口,如例4所示。

例4 MyBean2.java

 1  package cn.itcast.chapter05.listener;

 2  import javax.servlet.http.HttpSessionActivationListener;

 3  import javax.servlet.http.HttpSessionEvent;

 4  import java.io.Serializable;

 5  public class MyBean2 implements HttpSessionActivationListener,

 6  Serializable{

 7    private String name;

 8    private int age;

 9    public String getName() {

 10     return name;

 11   }

 12   public void setName(String name) {

 13     this.name = name;

 14   }

 15   public int getAge() {

 16     return age;

 17   }

 18   public void setAge(int age) {

 19     this.age = age;

 20   }

 21   public void sessionDidActivate(HttpSessionEvent arg0) {

 22     System.out.println("MyBean2的对象活化了...");

 23   }

 24   public void sessionWillPassivate(HttpSessionEvent arg0) {

 25     System.out.println("MyBean2的对象钝化了...");

 26   }

 27 }

例4的代码修改好了以后,接着在浏览器中依次访问write.jsp将MyBean2对象存入Session域中,然后再访问read.jsp页面获取存入对象的信息,如图5所示。

图5 read.jsp

从图5可以看出,从Session域中已经获取到了MyBean2对象的信息,稍后就会在控制台显示MyBean2对象钝化了,如图6所示。

图6 控制台窗口

为了证实MyBean2对象确实钝化了,并存储在硬盘中,接下来再次访问read.jsp页面,如图7所示。

图7 read.jsp

从图7所示,当MyBean2对象钝化后,还可以从Session域中获取该对象,说明MyBean2对象从硬盘中被读取到内存中,此时,控制台窗口会显示MyBean2对象活化了,如图8所示。

图8 控制台窗口

需要注意的是,要想监听JavaBean对象在Session域中的状态时,JavaBean对象一定要实现Serializable接口,只有实现该接口后,JavaBean对象才会被持久化到硬盘上,才能演示对象钝化和活化的状态。

:动手体验:统计登录用户人数

本小节讲解的HttpSessionBindingListener不仅可以统计在线用户的数量,还可以统计在线用户的名单,接下来分步骤讲解如何使用HttpSessionBindingListener统计在线用户的名单。

(1)在chapter05工程中创建cn.itcast.chapter05.entity包,在该包中编写一个User类,用于封装一个用户信息,该类实现了HttpSessionBindingListener接口,并实现该接口中的valueBound()方法和valueUnbound()方法,如例5所示。

例5 User.java

 1  package cn.itcast.chapter05.entity;

 2  import javax.servlet.http.*;

 3  public class User implements HttpSessionBindingListener {

 4    private String username;

 5    private String password;

 6    private String id;

 7    public String getUsername() {

 8      return username;

 9    }

 10   public void setUsername(String username) {

 11     this.username = username;

 12   }

 13   public String getPassword() {

 14     return password;

 15   }

 16   public void setPassword(String password) {

 17     this.password = password;

 18   }

 19   public String getId() {

 20     return id;

 21   }

 22   public void setId(String id) {

 23     this.id = id;

 24   }

 25   public void valueBound(HttpSessionBindingEvent event) {

 26     // 将user存入列表

 27     OnlineUser.getInstance().addUser(this);

 28   }

 29   public void valueUnbound(HttpSessionBindingEvent event) {

 30     OnlineUser.getInstance().removeUser(this);

 31   }

 32 }

(2)在chapter05工程的cn.itcast.chapter05.entity包中,编写一个OnlineUser类,OnlineUser类是一个单例模式的类,这是因为OnlineUser类的对象是用于存储和获取在线用户的列表,而这个列表对于所有的页面来说都应该是同一个,所以将OnlineUser类设计成单例模式,这样所有的类访问的就是同一个OnlineUser对象,OnlineUser类的代码如例6所示。

例6 OnlineUser.java

 1  package cn.itcast.chapter05.entity;

 2  import java.util.*; 

 3  public class OnlineUser {

 4    private OnlineUser() {}

 5    private static OnlineUser instance = new OnlineUser();

 6    public static OnlineUser getInstance() {

 7      return instance;

 8    }

 9    private Map userMap=new HashMap<>();

 10   // 将用户添加至列表

 11   public void addUser(User user) {

 12     userMap.put(user.getId(), user.getUsername());

 13   }

 14   // 将用户移除列表

 15   public void removeUser(User user) {

 16     userMap.remove(user.getId());

 17   }

 18   // 返回用户列表

 19   public Map getOnlineUsers() {

 20     return userMap;

 21   }

 22 }

(3)在chapter05工程的WebContent根目录中,编写一个login.jsp页面,该页面输入用户的登录名和密码,完成用户登录功能,例7所示。

例7 login.jsp

 1  <%@ page language="java" contentType="text/html; 

 2  charset=utf-8" pageEncoding="utf-8"%>

 3  <html>

 4  <head>

 5  <title>Insert title here</title>

 6  </head>

 7  <body>

 8    <center>

 9      <h3>用户登录</h3>

 10   </center>

 11   <form 

 12    action="${pageContext.request.contextPath }/LoginServlet"

 13     method="post">

 14     <table border="1" width="550px" cellpadding="0" 

 15      cellspacing="0" align="center">

 16       <tr>

 17         <td height="35" align="center">用户名:</td>

 18         <td>&nbsp;&nbsp;&nbsp;

 19             <input type="text" name="username" />

 20           </td>

 21       </tr>

 22       <tr>

 23         <td height="35" align="center">&nbsp; 码:</td>

 24         <td>&nbsp;&nbsp;&nbsp;

 25           <input type="password" name="password" />

 26         </td>

 27       </tr>

 28       <tr>

 29         <td height="35" colspan="2" align="center">

 30             <input type="submit" value="登录" />

 31             &nbsp;&nbsp;&nbsp;&nbsp;

 32             <input type="reset" value="重置" />

 33           </td>

 34       </tr>

 35     </table>

 36   </form>

 37 </body>

 38 </html>

(4)在chapter05工程中创建cn.itcast.chapter05.servlet包,在该包中编写一个LoginServlet类,用于处理用户登录请求。如果用户登录成功就将该用户的信息封装到User中存入Session对象,如例8所示。

例8 LoginServlet.java

 1  package cn.itcast.chapter05.servlet;

 2  import java.io.*;

 3  import java.util.*;

 4  import javax.servlet.*;

 5  import javax.servlet.http.*;

 6  import cn.itcast.chapter05.entity.*;

 7  public class LoginServlet extends HttpServlet {

 8    public void doGet(HttpServletRequest request, 

 9        HttpServletResponse response) throws ServletException, 

 10        IOException {

 11     request.setCharacterEncoding("utf-8");

 12     String username = request.getParameter("username");

 13     String password = request.getParameter("password");

 14     if (username != null && !username.trim().equals("")) {

 15       // 登录成功

 16       User user = new User();

 17       user.setId(UUID.randomUUID().toString());

 18       user.setUsername(username);

 19       user.setPassword(password);

 20       request.getSession().setAttribute("user", user);

 21       Map users = OnlineUser.getInstance().getOnlineUsers();

 22       request.setAttribute("users", users);

 23       request.getRequestDispatcher("/showuser.jsp").

 24        forward(request, response);

 25     } else {

 26       request.setAttribute("errorMsg", "用户名或密码错");

 27       request.getRequestDispatcher("/login.jsp").

 28        forward(request,response);

 29     }

 30   }

 31   public void doPost(HttpServletRequest request, 

 32        HttpServletResponse response) throws ServletException, 

 33        IOException {

 34     doGet(request, response);

 35   }

 36 }

(5)在chapter05工程的WebContent根目录中,编写一个showuser.jsp页面,该页面用于显示所有用户的登录信息以及当前登录的用户,如例9所示。

例9 showuser.jsp

 1  <%@ page language="java" contentType="text/html; charset=utf-8"

 2    pageEncoding="utf-8"%>

 3  <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

 4  <html>

 5  <head>

 6  <title>Insert title here</title>

 7  </head>

 8  <body>

 9    <c:choose>

 10     <c:when test="${sessionScope.user==null }">

 11    <a href="${pageContext.request.contextPath}/login.jsp">

 12        登录

 13     </a>

 14    <br>

 15     </c:when>

 16     <c:otherwise>

 17       欢迎你,${sessionScope.user.username }!

 18     <a href="${pageContext.request.contextPath}/LogoutServlet">

 19        退出

 20     </a>

 21     </c:otherwise>

 22   </c:choose>

 23   <hr>

 24   在线用户列表

 25   <br>

 26   <c:forEach var="user" items="${requestScope.users }">

 27     ${user.value }

 28   </c:forEach>

 29 </body>

 30 </html>

(6)在chapter05工程的cn.itcast.chapter05.servlet包中,编写一个LogoutServlet类,用于注销用户登录信息,用户被注销后会跳转到showuser.jsp页面,重新显示当前在线用户列表,如例10所示。

例10 LogoutServlet.java

 1  package cn.itcast.chapter05.servlet;

 2  import java.io.*;

 3  import java.util.*;

 4  import javax.servlet.*;

 5  import javax.servlet.http.*;

 6  import cn.itcast.chapter05.entity.*;

 7  public class LogoutServlet extends HttpServlet {

 8    public void doGet(HttpServletRequest request, 

 9        HttpServletResponse response)

 10       throws ServletException, IOException {

 11     request.getSession().removeAttribute("user");

 12     Map users = OnlineUser.getInstance().getOnlineUsers();

 13     request.setAttribute("users", users);

 14     request.getRequestDispatcher("/showuser.jsp").

 15     forward(request, response);

 16   }

 17   public void doPost(HttpServletRequest request, 

 18    HttpServletResponse response)

 19   throws ServletException, IOException {

 20     doGet(request, response);

 21   }

 22 }

(7)重新启动Web服务器,打开IE浏览器,在地址栏中输入http://localhost:8080/chapter05/login.jsp,访问login.jsp页面,在该页面中输入用户名itcast、密码123,如图9所示。

图9 登录页面

(8)点击图9所示的登录按钮,此时,浏览器窗口中会显示在线的用户以及当前登录的用户,如图10所示。

图10 登录成功页面

(9)在IE浏览器中的工具栏中,选择【文件】菜单中的【新建会话】,以新建会话的方式开启一个浏览器窗口,在该窗口中再次输入http://localhost:8080/chapter05/login.jsp,访问login.jsp页面,在该页面中输入用户名itheima、密码123,然后点击登录按钮,此时,浏览器窗口中显示的结果如图11所示。

图11 登录成功页面

从图11可以看出,现在有两个用户在线,分别是itcast和itheima,如果想注销当前正在登录的用户,可以点击退出超连接,此时,只会显示在线用户列表,不会显示当前登录用户,如图12所示。

图12 退出登录页面

点击此处
隐藏目录