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>
19 <input type="text" name="username" />
20 </td>
21 </tr>
22 <tr>
23 <td height="35" align="center">密 码:</td>
24 <td>
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
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 退出登录页面