监听器是指专门用于在其他对象身上发生的事件或者状态改变进行监听和相应处理的对象,当被监听的对象发生变化时立即采取相应的行动。
例如我们要实现统计一个网站的在线人数,就可以在Web应用应用程序服务器上设置监听器用来监听session对象——设置一个全局变量,session创建的时候该变量加1,session销毁的时候该变量减1.(这个变量其实就是用户在线人数)。
Web监听器是Servlet规范定义的特殊类,可以监听客户端的请求和服务端的操作。监听的对象有:ServletContext、HttpSession、ServletRequest分别对应JSP9大内置对象中的application、session和request。用于监听ServletContext、HttpSession、ServletRequest等域对象的创建和销毁事件,监听域对象属性发生修改的事件。监听器对象可以在事件发生前、发生后做一些必要的处理。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <display-name></display-name> 8 <welcome-file-list> 9 <welcome-file>index.jsp</welcome-file> 10 </welcome-file-list> 11 12 <!-- 配置监听器 --> 13 <listener> 14 <listener-class>listener.FirstListener</listener-class> 15 </listener> 16 17 </web-app>
在一个web.xml中可以注册多个Servlet监听器,监听器的加载顺序是按照web.xml文件中的顺序加载的。
如果我们在web.xml文件中同时配置了监听器、过滤器和Servlet,那么它们的优先级是:
按照监听的对象,可以将监听器划分为如下:
按照监听的事件可以划分为以下3种:
该监听器中有2个方法(两个方法中都有ServletContextEvent参数。可以通过它获得当前应用的上下文对象ServletContext):
1 public void contextInitialized(ServletContextEvent sce) // ServletContext创建时调用 2 public void contextDestroyed(ServletContextEvent sce) // ServletContext销毁时调用
这个监听器的主要用途是做一些定时器、加载一些全局属性对象、创建全局的数据库连接、加载一些缓存信息。
1 package listener; 2 3 import javax.servlet.ServletContextEvent; 4 import javax.servlet.ServletContextListener; 5 6 public class FirstListener implements ServletContextListener { 7 8 public void contextInitialized(ServletContextEvent servletContextEvent) { 9 10 String initParam = servletContextEvent.getServletContext().getInitParameter("initParam"); 11 // 可以在这里通过Servlet的setAttribute()方法设置全局的属性对象,从其他地方可以获取到这个属性对象 12 servletContextEvent.getServletContext().setAttribute("name", "object"); 13 System.out.println("===========contextInitialized,initParam = " + initParam); 14 15 } 16 17 public void contextDestroyed(ServletContextEvent servletContextEvent) { 18 System.out.println("=================contextDestroyed================"); 19 // 可以在这里关闭数据库连接 20 } 21 22 }
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <display-name></display-name> 8 <welcome-file-list> 9 <welcome-file>index.jsp</welcome-file> 10 </welcome-file-list> 11 12 <!-- 配置监听器 --> 13 <listener> 14 <listener-class>listener.FirstListener</listener-class> 15 </listener> 16 17 <!-- Servlet的初始化参数 --> 18 <context-param> 19 <param-name>initParam</param-name> 20 <param-value>123456</param-value> 21 </context-param> 22 23 </web-app>
将项目部署到Tomcat,运行:
该监听器的主要作用是统计在线人数、记录访问日志【在后台统计访问时间、IP信息做一些统计数据】
1 package listener; 2 3 import java.util.Date; 4 5 import javax.servlet.http.HttpSessionEvent; 6 import javax.servlet.http.HttpSessionListener; 7 8 public class MyHttpSessionListener implements HttpSessionListener { 9 10 public void sessionCreated(HttpSessionEvent httpSessionEvent) { 11 System.out.println("===================================="); 12 System.out.println("session创建于" + new Date()); 13 } 14 15 public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { 16 System.out.println("===================================="); 17 System.out.println("session销毁于" + new Date()); 18 } 19 20 }
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <display-name></display-name> 8 <welcome-file-list> 9 <welcome-file>index.jsp</welcome-file> 10 </welcome-file-list> 11 12 <!-- 注册监听器 --> 13 <listener> 14 <listener-class>listener.MyHttpSessionListener</listener-class> 15 </listener> 16 <!-- 配置session的超时为1min --> 17 <session-config> 18 <session-timeout>1</session-timeout> 19 </session-config> 20 21 </web-app>
该监听器的主要作用是:读取参数、记录访问历史。
1 package listener; 2 3 import java.util.Date; 4 5 import javax.servlet.ServletRequestEvent; 6 import javax.servlet.ServletRequestListener; 7 8 public class MyServletRequestListener implements ServletRequestListener { 9 10 public void requestInitialized(ServletRequestEvent servletRequestEvent) { 11 System.out.println("================================="); 12 System.out.println("request创建于" + new Date()); 13 14 String name = servletRequestEvent.getServletRequest().getParameter("name"); 15 System.out.println("从URL中获得参数name,name = " + name); 16 } 17 18 public void requestDestroyed(ServletRequestEvent servletRequestEvent) { 19 System.out.println("================================="); 20 System.out.println("request销毁于" + new Date()); 21 22 } 23 24 }
在web.xml中配置以上监听器,运行程序:
有3种,分别是ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener接口,在各个接口中都有attributeAdded()、attributeRemoved()和attributeReplaced()这3个方法。
分别创建3个类:
MyServletContextAttributeListener 继承自 ServletContextAttributeListener,
MyHttpSessionAttributeListener 继承自 HttpSessionAttributeListener,
MyServletRequestAttributeListener 继承自 ServletRequestAttributeListener。
并在web.xml中配置以上的3个监听器
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <display-name></display-name> 8 <welcome-file-list> 9 <welcome-file>index.jsp</welcome-file> 10 </welcome-file-list> 11 12 <listener> 13 <listener-class>listener.MyServletRequestAttributeListener</listener-class> 14 </listener> 15 <listener> 16 <listener-class>listener.MyHttpSessionAttributeListener</listener-class> 17 </listener> 18 <listener> 19 <listener-class>listener.MyServletContextAttributeListener</listener-class> 20 </listener> 21 </web-app>
1 package listener; 2 3 import javax.servlet.ServletContextAttributeEvent; 4 import javax.servlet.ServletContextAttributeListener; 5 6 public class MyServletContextAttributeListener implements 7 ServletContextAttributeListener { 8 9 public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) { 10 System.out.println("ServletContext_attributeAdded:" + servletContextAttributeEvent.getName()); 11 } 12 13 public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) { 14 System.out.println("ServletContext_attributeRemoved:" + servletContextAttributeEvent.getName()); 15 } 16 17 public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) { 18 System.out.println("ServletContext_attributeReplaced:" + servletContextAttributeEvent.getName()); 19 } 20 21 }
1 package listener; 2 3 import javax.servlet.ServletRequestAttributeEvent; 4 import javax.servlet.ServletRequestAttributeListener; 5 6 public class MyServletRequestAttributeListener implements 7 ServletRequestAttributeListener { 8 9 public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) { 10 System.out.println("ServletRequest_attributeAdded:" + servletRequestAttributeEvent.getName()); 11 } 12 13 public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) { 14 System.out.println("ServletRequest_attributeRemoved:"+ servletRequestAttributeEvent.getName()); 15 } 16 17 public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) { 18 System.out.println("ServletRequest_attributeReplaced:"+ servletRequestAttributeEvent.getName()); 19 } 20 21 }
1 package listener; 2 3 import javax.servlet.http.HttpSessionAttributeListener; 4 import javax.servlet.http.HttpSessionBindingEvent; 5 6 public class MyHttpSessionAttributeListener implements 7 HttpSessionAttributeListener { 8 9 public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) { 10 System.out.println("HttpSession_attributeAdded:" + httpSessionBindingEvent.getName()); 11 } 12 13 public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) { 14 System.out.println("HttpSession_attributeRemoved:" + httpSessionBindingEvent.getName()); 15 } 16 17 public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) { 18 System.out.println("HttpSession_attributeReplaced:" + httpSessionBindingEvent.getName()); 19 } 20 21 }
1 <%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <base href="<%=basePath%>"> 11 <title>主页</title> 12 </head> 13 14 <body> 15 <h1>这是主页</h1> 16 <a href="init.jsp">跳转到init.jsp分别向ServletRequest、HttpSession、ServletContext中设置值</a><br /> 17 <a href="destroy.jsp">跳转到destroy.jsp销毁向ServletRequest、HttpSession、ServletContext中设置的值</a> 18 </body> 19 </html>
1 <%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 6 // 初始化值【添加属性】,监听器将会执行各自的attributeAdded方法 7 request.setAttribute("requestName", "requestValue"); 8 request.getSession().setAttribute("sessionName", "sessionValue"); 9 request.getSession().getServletContext().setAttribute("contextName", "contextValue"); 10 11 %> 12 13 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 14 <html> 15 <head> 16 <base href="<%=basePath%>"> 17 <title>该页面用于初始化值</title> 18 </head> 19 20 <body> 21 <h1>这是初始化值的界面!</h1> 22 <a href="init.jsp">跳转到init.jsp分别向ServletRequest、HttpSession、ServletContext中设置值</a><br /> 23 <a href="destroy.jsp">跳转到destroy.jsp销毁向ServletRequest、HttpSession、ServletContext中设置的值</a> 24 </body> 25 </html>
1 <%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 6 // 删除属性,监听器将会执行各自的attributeRemoved方法 7 request.removeAttribute("requestName"); 8 request.getSession().removeAttribute("sessionName"); 9 request.getSession().getServletContext().removeAttribute("contextName"); 10 %> 11 12 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 13 <html> 14 <head> 15 <base href="<%=basePath%>"> 16 <title>销毁界面</title> 17 </head> 18 19 <body> 20 <h1>销毁界面</h1> 21 <a href="init.jsp">跳转到init.jsp分别向ServletRequest、HttpSession、ServletContext中设置值</a><br /> 22 <a href="destroy.jsp">跳转到destroy.jsp销毁向ServletRequest、HttpSession、ServletContext中设置的值</a> 23 </body> 24 </html>
三、绑定到HttpSession域中的对象的状态的事件监听器
HttpSession的状态如下图所示:
一般情况下session是保存在服务器内存的,服务器为每一个在线用户保存session对象,当在线用户很多时,session内存的开销将是非常巨大的——直接导致web服务器性能的降低。session的钝化机制就是将不经常使用的session对象序列化存储到文件系统或者是数据库中。需要使用这些session的时候进行反序列化到内存中。整个过程由服务器自动完成。
org.apache.catalina.session.StandarManger
钝化后的文件的位置:Tomcat安装路径/work/Catalina/hostname/applicationname/SESSIONS.ser
默认情况下,Tomcat提供两个钝化驱动类:
org.apache.Catalina.FileStore和org.apache.Catalina.JDBCStore
在Servlet规范中提供了两个接口HttpSessionBindingListener和HttpSessionActivationListener分别对应绑定-解除绑定方法和钝化-活化方法。这两个监听器不需要在web.xml中注册。
HttpSessionBandingListener接口:
创建一个User实体类(普通的javabean)实现HttpSessionBandingListener接口。
1 package entity; 2 3 import javax.servlet.http.HttpSessionBindingEvent; 4 import javax.servlet.http.HttpSessionBindingListener; 5 6 public class User implements HttpSessionBindingListener { 7 8 private String username; 9 private String password; 10 11 public String getUsername() { 12 return username; 13 } 14 15 public void setUsername(String username) { 16 this.username = username; 17 } 18 19 public String getPassword() { 20 return password; 21 } 22 23 public void setPassword(String password) { 24 this.password = password; 25 } 26 27 public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) { 28 System.out.println("绑定,属性名:"+ httpSessionBindingEvent.getName()); 29 30 } 31 32 public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) { 33 System.out.println("解除绑定,属性名:"+ httpSessionBindingEvent.getName()); 34 35 } 36 37 }
在init.jsp中向session中设置对象:
<% request.getSession().setAttribute("currentUser", new User()); %>
在destroy.jsp中销毁设置的对象:
<% request.getSession().removeAttribute("currentUser"); %>
运行结果:
接下来我们继续为以上的User类实现HttpSessionActivationListener接口和Serializable接口。
1 /** 活化 */ 2 public void sessionDidActivate(HttpSessionEvent httpSessionEvent) { 3 System.out.println("活化:" + httpSessionEvent.getSource()); 4 5 } 6 7 /** 钝化 */ 8 public void sessionWillPassivate(HttpSessionEvent httpSessionEvent){ 9 System.out.println("钝化:" + httpSessionEvent.getSource()); 10 11 }
运行程序,可以发现向session中设置了User对象,在session没有过期的时候,服务器重启的时候会执行钝化方法,将当前的session保存在项目目录下的SESSIONS.ser。当服务器重启的时候会执行激活方法,将序列化为文件的session读入内存,并删除SESSIONS.ser。即:服务器重启不会丢失用户数据。
使用注解将监听器类声明为一个监听器,不需要在web.xml中注册监听器,但是这样做有一个弊端:无法设置监听器的顺序。@WebListener Annotation只有一个value属性【String类型】——用于描述该监听器。
见如下代码:
1 package listener; 2 3 import javax.servlet.ServletContextEvent; 4 import javax.servlet.ServletContextListener; 5 import javax.servlet.annotation.WebListener; 6 @WebListener(value = "这是Servlet 3.0 的监听器") 7 public class MyServletContextListener implements ServletContextListener { 8 9 @Override 10 public void contextDestroyed(ServletContextEvent servletContextEvent) { 11 System.out.println("============== contextDestroyed ============"); 12 13 } 14 15 @Override 16 public void contextInitialized(ServletContextEvent servletContextEvent) { 17 System.out.println("============== contextInitialized ============"); 18 19 } 20 21 }
运行结果:
该项目使用J2EE6.0创建。
1. 创建一个类继承自HttpSessionListenr。并加上@WebListener Annotation。
1 package listener; 2 3 import javax.servlet.annotation.WebListener; 4 import javax.servlet.http.HttpSessionEvent; 5 import javax.servlet.http.HttpSessionListener; 6 @WebListener 7 public class MyHttpSessionListener implements HttpSessionListener { 8 9 private static int userNumber = 0; // 用户在线人数 10 11 @Override 12 public void sessionCreated(HttpSessionEvent httpSessionEvent) { 13 // 每创建一个session,在线人数加1 14 userNumber++; 15 // 把在线人数放入上下文环境中,便于在整个web应用中都可以获得在线人数 16 httpSessionEvent.getSession().getServletContext().setAttribute("userNumber", userNumber); 17 } 18 19 @Override 20 public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { 21 userNumber--; 22 httpSessionEvent.getSession().getServletContext().setAttribute("userNumber", userNumber); 23 } 24 25 }
2.在index.jsp中显示在线人数:
当前在线人数:${userNumber}
以上程序就完成了一个简单的在线人数的统计。session并不能获取用户的IP地址,request对象可以获取ip地址。如果我们想要看到各个访客的来源IP,就必须做以下改进:
1 package listener; 2 3 import java.util.ArrayList; 4 5 import javax.servlet.annotation.WebListener; 6 import javax.servlet.http.HttpSessionEvent; 7 import javax.servlet.http.HttpSessionListener; 8 9 import util.SessionUtil; 10 11 import entity.User; 12 @WebListener 13 public class MyHttpSessionListener implements HttpSessionListener { 14 15 private static int userNumber = 0; // 用户在线人数 16 17 @Override 18 public void sessionCreated(HttpSessionEvent httpSessionEvent) { 19 // 每创建一个session,在线人数加1 20 userNumber++; 21 // 把在线人数放入上下文环境中,便于在整个web应用中都可以获得在线人数 22 httpSessionEvent.getSession().getServletContext().setAttribute("userNumber", userNumber); 23 } 24 25 @Override 26 public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { 27 userNumber--; 28 httpSessionEvent.getSession().getServletContext().setAttribute("userNumber", userNumber); 29 30 ArrayList<User>userList = null; 31 32 userList = (ArrayList<User>) httpSessionEvent.getSession().getServletContext().getAttribute("userList");//在线用户list 33 User user = SessionUtil.getUserBySessionId(userList, httpSessionEvent.getSession().getId()); 34 if (user!=null) { 35 userList.remove(user);//在session销毁时从在线的用户列表中移除该用户 36 } 37 } 38 39 }
1 package listener; 2 3 import java.util.ArrayList; 4 import java.util.Date; 5 6 import javax.servlet.ServletRequestEvent; 7 import javax.servlet.ServletRequestListener; 8 import javax.servlet.annotation.WebListener; 9 import javax.servlet.http.HttpServletRequest; 10 11 import util.SessionUtil; 12 13 import entity.User; 14 @WebListener 15 public class MyServletRequestListener implements ServletRequestListener { 16 17 private ArrayList<User>userList; //在线用户列表 18 19 @Override 20 public void requestInitialized(ServletRequestEvent servletRequestEvent) { 21 22 userList = (ArrayList<User>) servletRequestEvent.getServletContext().getAttribute("userList"); 23 if (userList==null) { 24 userList = new ArrayList<User>();//首次用户请求的时候创建一个新的在线用户列表 25 } 26 27 HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); 28 String sessionId = request.getSession().getId(); 29 30 // 如果当前用户没有在在线用户列表中,将其加入 31 if (SessionUtil.getUserBySessionId(userList, sessionId)==null) { 32 User user = new User(); 33 user.setSessionId(sessionId); 34 user.setIp(request.getRemoteAddr()); 35 user.setFirstAccseeTime(new Date()); 36 userList.add(user); 37 } 38 servletRequestEvent.getServletContext().setAttribute("userList", userList);//将在线用户的列表保存到应用的上下文对象 39 } 40 41 @Override 42 public void requestDestroyed(ServletRequestEvent servletRequestEvent) { 43 44 } 45 46 }
1 package util; 2 3 import java.util.ArrayList; 4 5 import entity.User; 6 7 public class SessionUtil { 8 9 /** 根据用户的sessionId从在线用户列表中找出用户 */ 10 public static User getUserBySessionId(ArrayList<User>userList,String sessionId){ 11 12 for (User user : userList) { 13 if (user.getSessionId().equals(sessionId)) { 14 return user; 15 } 16 } 17 return null; 18 } 19 20 }
1 package entity; 2 3 import java.util.Date; 4 5 public class User { 6 7 private String sessionId; // sessionID 8 private String ip; // IP 9 private Date firstAccseeTime;// 第一次访问时间 10 11 public String getSessionId() { 12 return sessionId; 13 } 14 15 public void setSessionId(String sessionId) { 16 this.sessionId = sessionId; 17 } 18 19 public String getIp() { 20 return ip; 21 } 22 23 public void setIp(String ip) { 24 this.ip = ip; 25 } 26 27 public Date getFirstAccseeTime() { 28 return firstAccseeTime; 29 } 30 31 public void setFirstAccseeTime(Date firstAccseeTime) { 32 this.firstAccseeTime = firstAccseeTime; 33 } 34 35 }
1 <%@page import="entity.User"%> 2 <%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%> 3 <% 4 String path = request.getContextPath(); 5 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 6 %> 7 8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 9 <html> 10 <head> 11 <base href="<%=basePath%>"> 12 <title>主页</title> 13 </head> 14 15 <body> 16 当前在线人数:${userNumber}<br /> 17 <% 18 ArrayList<User>userList =(ArrayList<User>) request.getServletContext().getAttribute("userList"); 19 20 if(userList!=null){ 21 for(User user:userList){ 22 %> 23 IP:<%=user.getIp() %>,sessionID:<%=user.getSessionId() %>,第一次访问的时间:<%=user.getFirstAccseeTime() %><br /> 24 <% 25 } 26 } 27 %> 28 </body> 29 </html>
项目地址:https://git.oschina.net/gaopengfei/CountTheOnlineUser.git