监听器Listener
监听器基础
监听器介绍
监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口
的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。
Servlet规范中的八大监听器如下:
名称 | 描述 |
---|---|
ServletContextListener | 在Container加载Web应用程序时(例如启动 Container之后),会呼叫contextInitialized(),而当容器移除Web应用程序时,会呼叫contextDestroyed ()方法。 |
HttpSessionListener | 在session (HttpSession)对象建立或被消灭时,会分别呼叫这两个方法。 |
HttpSessionActivationListener | ctivate与Passivate是用于置换对象的动作,当session对象为了资源利用或负载平衡等原因而必须暂时储存至硬盘或其它储存器时(透 过对象序列化),所作的动作称之为Passivate,而硬盘或储存器上的session对象重新加载JVM时所采的动作称之为Activate,所以容 易理解的,sessionDidActivate()与 sessionWillPassivate()分别于Activeate后与将Passivate前呼叫。 |
ServletRequestListener | 在request(HttpServletRequest)对象建立或被消灭时,会分别呼叫这两个方法。 |
ServletContextAttributeListener | 若有对象加入为application(ServletContext)对象的属性,则会呼叫attributeAdded(),同理在置换属性与移除属性时,会分别呼叫attributeReplaced()、attributeRemoved()。 |
HttpSessionAttributeListener | 有对象加入为session(HttpSession)对象的属性,则会呼叫attributeAdded(),同理在置换属性与移除属性时,会分别呼叫attributeReplaced()、 attributeRemoved()。 |
ServletRequestAttributeListener | 若有对象加入为request(HttpServletRequest)对象的属性,则会呼叫attributeAdded(),同理在置换属性与移除属性时,会分别呼叫attributeReplaced()、 attributeRemoved()。 |
HttpSessionBindingListener | 实现HttpSessionBindingListener接 口的类别,其实例如果被加入至session(HttpSession)对象的属性中,则会呼叫 valueBound(),如果被从session(HttpSession)对象的属性中移除,则会呼叫valueUnbound(),实现 HttpSessionBindingListener接口的类别不需在web.xml中设定。 |
实现上面这几个接口的类别,除了HttpSessionBindingListener外,必须在web.xml中向容器注册,容器才会在对应的事件发生时呼叫对应的类别,如:
<listener>
<description>XXX监听器</description>
<listener-class>com.legend.listener.XXXListener</listener-class>
</listener>
- 监听window窗口事件
public class Demo1 {
/**
*java的事件监听机制
*1、事件监听涉及到三个组件:事件源、事件对象、事件监听器
*2、当事件源上发生某一个动作时,它会调用事件监听器的一个方法,并在调用该方法时把事件对象传递进去,
* 开发人员在监听器中通过事件对象,就可以拿到事件源,从而对事件源进行操作。
*/
public static void main(String[] args) {
Frame f = new Frame();
f.setSize(400, 400);
f.setVisible(true);
//注册事件监听器
f.addWindowListener(new WindowListener(){
public void windowActivated(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
/**
* 当window窗体关闭时就会WindowListener这个监听器监听到,
* 监听器就会调用windowClosing方法处理window窗体关闭时的动作
*/
public void windowClosing(WindowEvent e) {
//通过事件对象e来获取事件源对象
Frame f = (Frame) e.getSource();
System.out.println(f+"窗体正在关闭");
f.dispose();
}
public void windowDeactivated(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { }
public void windowIconified(WindowEvent e) { }
public void windowOpened(WindowEvent e) { }
});
}
}
-
设计一个可以被别的对象监听的对象
我们平时做开发的时候,我们是写监听器去监听其他对象,那么我们如果想设计一个对象,让这个对象可以被别的对象监听又该怎么做呢,可以按照严格的事件处理模型来设计一
个对象,这个对象就可以被别的对象监听,事件处理模型涉及到三个组件:事件源、事件对象、事件监听器。
下面我们来按照事件处理模型来设计一个Person对象,具体代码如下:
public class Person {
private PersonListener mListener;
public void eat() {
if (mListener != null) {
mListener.doeat(new Event(this));
}
}
public void run() {
if (mListener != null) {
mListener.dorun(new Event(this));
}
}
public void registListener(PersonListener listener) {
this.mListener = listener;
}
public interface PersonListener {
// 监听Person对象eat(吃)这个行为动作
void doeat(Event e);
// 监听Person对象run(跑)这个行为动作
void dorun(Event e);
}
class Event {
private Person mPerson;
public Event(Person person) {
mPerson = person;
}
public Person getPerson() {
return mPerson;
}
public void setPerson(Person person) {
mPerson = person;
}
}
}
经过这样的设计之后,Peron类的对象就是可以被其他对象监听了。测试代码如下:
public class PersonTest {
public static void main(String[] args) {
Person person = new Person();
person.registListener(new Person.PersonListener() {
@Override
public void doeat(Person.Event e) {
Person p = e.getPerson();
System.out.println(e + "在吃东西");
}
@Override
public void dorun(Person.Event e) {
Person p = e.getPerson();
System.out.println(p + "在跑步");
}
});
person.eat();
person.run();
}
}
运行结果:
com.legend.listener.Person$Event@1b6d3586在吃东西
com.legend.listener.Person@4554617c在跑步
JavaWeb监听器
JavaWeb中的监听器是Servlet规范中定义的一种特殊类,它用于监听web应用程序中的ServletContext, HttpSession和 ServletRequest等域对象的创建与销毁事件,
以及监听这些域对象中的属性发生修改的事件。
在Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别为ServletContext,HttpSession和ServletRequest这三个域对象
Servlet规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型:
监听域对象自身的创建和销毁的事件监听器。
监听域对象中的属性的增加和删除的事件监听器。
监听绑定到HttpSession域中的某个对象的状态的事件监听器
-
监听ServletContext域对象的创建和销毁
ServletContextListener接口用于监听ServletContext对象的创建和销毁事件。实现了ServletContextListener接口的类都可以对ServletContext对象的创建和销毁进行监听。
当ServletContext对象被创建时,激发contextInitialized (ServletContextEvent sce)方法。
当ServletContext对象被销毁时,激发contextDestroyed(ServletContextEvent sce)方法。
ServletContext域对象创建和销毁时机:
创建:服务器启动针对每一个Web应用创建ServletContext
销毁:服务器关闭前先关闭代表每一个web应用的ServletContext
范例:编写一个MyServletContextListener类,实现ServletContextListener接口,监听ServletContext对象的创建和销毁
1、编写监听器,代码如下所示:
// MyServletContextListener类实现了ServletContextListener接口,
// 因此可以对ServletContext对象的创建和销毁这两个动作进行监听。
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext对象创建");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext对象销毁");
}
}
2、在web.xml文件中注册监听器
<listener>
<description>ServletContextListener监听器</description>
<listener-class>com.legend.listener.MyServletContextListener</listener-class>
</listener>
经过这两个步骤,我们就完成了监听器的编写和注册,Web服务器在启动时,就会自动把在web.xml中配置的监听器注册到ServletContext对象上,这样开发好的
MyServletContextListener监听器就可以对ServletContext对象进行监听了。
-
监听HttpSession域对象的创建和销毁
HttpSessionListener 接口用于监听HttpSession对象的创建和销毁
创建一个Session时,激发sessionCreated (HttpSessionEvent se) 方法
销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se) 方法。
范例:编写一个MyHttpSessionListener类,实现HttpSessionListener接口,监听HttpSession对象的创建和销毁
1、编写监听器,代码如下:
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println(httpSessionEvent.getSession() + "创建了!!");
System.out.println("创建好的HttpSession的id是:" + httpSessionEvent.getSession().getId());
}
/**
* HttpSession的销毁时机需要在web.xml中进行配置,如下:
*<session-config>
* <session-timeout>1</session-timeout>
* </session-config>
* 这样配置就表示session在1分钟之后就被销毁
*/
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("session销毁了!!");
}
}
2、在web.xml文件中注册监听器
<listener>
<description>HttpSessionListener监听器</description>
<listener-class>com.legend.listener.MyHttpSessionListener</listener-class>
</listener>
<!--配置HttpSession的销毁时机-->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
当我们访问jsp页面时,HttpSession对象就会创建,我们可以写一个jsp页面观察HttpSession对象创建的过程。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>HttpSessionListener监听器监听HttpSession对象的创建</title>
</head>
<body>
访问JSP页面,HttpSession就创建了,创建好的Session的Id是:
${pageContext.session.id}
</body>
</html>
输出结果:
访问JSP页面,HttpSession就创建了,创建好的Session的Id是: C37790FDA59BE34E76F219D25725F447
-
监听ServletRequest域对象的创建和销毁
ServletRequestListener接口用于监听ServletRequest 对象的创建和销毁
Request对象被创建时,监听器的requestInitialized(ServletRequestEvent sre)方法将会被调用
Request对象被销毁时,监听器的requestDestroyed(ServletRequestEvent sre)方法将会被调用
范例:编写一个MyServletRequestListener类,实现ServletRequestListener接口,监听ServletRequest对象的创建和销毁
1、编写监听器,代码如下:
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "销毁了!!");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "创建了!!");
}
}
2、在web.xml文件中注册监听器
<!--注册针对ServletRequest对象进行监听的监听器-->
<listener>
<description>ServletRequestListener监听器</description>
<listener-class>me.gacl.web.listener.MyServletRequestListener</listener-class>
</listener>
域对象属性监听
域对象中属性的变更的事件监听器就是用来监听 ServletContext, HttpSession, HttpServletRequest 这三个对象中的属性变更信息事件的监听器。
这三个监听器接口分别是ServletContextAttributeListener, HttpSessionAttributeListener 和ServletRequestAttributeListener,这三个接口中都定义了三个方
法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。
-
attributeAdded方法
当向被监听对象中增加一个属性时,web容器就调用事件监听器的attributeAdded方法进行响应,这个方法接收一个事件类型的参数,监听器可以通过这个参数
来获得正在增加属性的域对象和被保存到域中的属性对象
各个域属性监听器中的完整语法定义为:
public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeReplaced(HttpSessionBindingEvent hsbe)
public void attributeRmoved(ServletRequestAttributeEvent srae)
-
attributeRemoved方法
当删除被监听对象中的一个属性时,web容器调用事件监听器的attributeRemoved方法进行响应各个域属性监听器中的完整语法定义为:
public void attributeRemoved(ServletContextAttributeEvent scae)
public void attributeRemoved (HttpSessionBindingEvent hsbe)
public void attributeRemoved (ServletRequestAttributeEvent srae)
-
attributeReplaced方法
当监听器的域对象中的某个属性被替换时,web容器调用事件监听器的attributeReplaced方法进行响应,各个域属性监听器中的完整语法定义为:
public void attributeReplaced(ServletContextAttributeEvent scae)
public void attributeReplaced (HttpSessionBindingEvent hsbe)
public void attributeReplaced (ServletRequestAttributeEvent srae)
-
ServletContextAttributeListener监听器范例
编写ServletContextAttributeListener监听器监听ServletContext域对象的属性值变化情况,代码如下:
public class MyServletContextAttributeListener implements
ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
String str = MessageFormat.format("ServletContext域对象中添加属性:{0}, 属性值是:{1}"
, servletContextAttributeEvent.getName(), servletContextAttributeEvent.getValue());
System.out.println(str);
}
@Override
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
String str = MessageFormat.format("ServletContext域对象中删除属性:{0}, 属性值是:{1}",
servletContextAttributeEvent.getName(), servletContextAttributeEvent.getValue());
System.out.println(str);
}
@Override
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
String str = MessageFormat.format("ServletContext域对象中替换了属性:{0}的值",
servletContextAttributeEvent.getName());
System.out.println(str);
}
}
在web.xml文件中注册监听器
<listener>
<description>MyServletContextAttributeListener监听器</description>
<listener-class>com.legend.listener.MyServletContextAttributeListener</listener-class>
</listener>
编写ServletContextAttributeListenerTest.jsp测试页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>ServletContextAttributeListener监听器测试</title>
</head>
<body>
<%
// 往application域对象添加属性
application.setAttribute("name", "legend");
// 替换application对象中name属性值
application.setAttribute("name", "vincent");
// 移除application域对象中name属性
application.removeAttribute("name");
%>
</body>
</html>
输出结果:
ServletContext域对象中添加属性:name, 属性值是:legend
ServletContext域对象中替换了属性:name的值
ServletContext域对象中删除属性:name, 属性值是:vincent
从运行结果中可以看到,ServletContextListener监听器成功监听到了ServletContext域对象(application)中的属性值的变化情况。
-
ServletRequestAttributeListener和HttpSessionAttributeListener监听器范例
编写监听器监听HttpSession和HttpServletRequest域对象的属性值变化情况,代码如下:
public class MyRequestAndSessionAttributeListener implements
HttpSessionAttributeListener, ServletRequestAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
String str = MessageFormat.format("ServletRequest域对象中添加了属性{0}, 属性值是:{1}",
httpSessionBindingEvent.getName(), httpSessionBindingEvent.getValue());
System.out.println(str);
}
@Override
public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
String str = MessageFormat.format("ServletRequest域对象中删除属性{0}, 属性值是:{1}",
httpSessionBindingEvent.getName(), httpSessionBindingEvent.getValue());
System.out.println(str);
}
@Override
public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
String str = MessageFormat.format("ServletRequest域对象替换属性{0}的值",
httpSessionBindingEvent.getName());
System.out.println(str);
}
@Override
public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {
String str = MessageFormat.format("HttpSession域对象中添加了属性{0}, 属性值是:{1}",
servletRequestAttributeEvent.getName(), servletRequestAttributeEvent.getValue());
System.out.println(str);
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {
String str = MessageFormat.format("HttpSession域对象中添加了属性{0}, 属性值是:{1}",
servletRequestAttributeEvent.getName(), servletRequestAttributeEvent.getValue());
System.out.println(str);
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {
String str = MessageFormat.format("HttpSession域对象替换属性{0}的值",
servletRequestAttributeEvent.getName());
System.out.println(str);
}
}
在web.xml文件中注册监听器
<listener>
<description>MyRequestAndSessionAttributeListener监听器</description>
<listener-class>com.legend.listener.MyRequestAndSessionAttributeListener</listener-class>
</listener>
编写RequestAndSessionAttributeListenerTest.jsp测试页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
// 往session域对象中添加属性
session.setAttribute("name", "legend");
// 替换session域对象中的属性
session.setAttribute("name", "vincent");
// 移除session域对象中的属性
session.removeAttribute("name");
// 往request域对象中添加属性
request.setAttribute("name", "legend");
// 替换request域对象中的属性
request.setAttribute("name", "vincent");
// 移除request域对象中的属性
request.removeAttribute("name");
%>
</body>
</html>
运行结果:
ServletRequest域对象中添加了属性name, 属性值是:legend
ServletRequest域对象替换属性name的值
ServletRequest域对象中删除属性name, 属性值是:vincent
HttpSession域对象中添加了属性name, 属性值是:legend
HttpSession域对象替换属性name的值
HttpSession域对象中添加了属性name, 属性值是:vincent
从结果中可以看到,HttpSessionAttributeListener和ServletRequestAttributeListener成功监听到HttpSession域对象和HttpServletRequest域对象的属性值变化情况。
Session绑定事件监听
保存在Session域中的对象可以有多种状态:
绑定(session.setAttribute("bean",Object))到Session中;
从 Session域中解除(session.removeAttribute("bean"))绑定;
随Session对象持久化到一个存储设备中;随Session对象从一个存储设备中恢复
Servlet 规范中定义了两个特殊的监听器接口"HttpSessionBindingListener和HttpSessionActivationListener"来帮助JavaBean 对象了解自己在Session域中的这些状态:
实现这两个接口的类不需要 web.xml 文件中进行注册。
-
HttpSessionBindingListener接口
实现了HttpSessionBindingListener接口的JavaBean对象可以感知自己被绑定到Session中和 Session中删除的事件
当对象被绑定到HttpSession对象中时,web服务器调用该对象的void valueBound(HttpSessionBindingEvent event)方法
当对象从HttpSession对象中解除绑定时,web服务器调用该对象的void valueUnbound(HttpSessionBindingEvent event)方法
范例:
public class JavaBeanDemo1 implements HttpSessionBindingListener {
private String name;
@Override
public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println(name + "被加到session中了");
}
@Override
public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println(name + "被session移除了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public JavaBeanDemo1(String name) {
this.name = name;
}
}
上述的JavaBeanDemo1这个javabean实现了HttpSessionBindingListener接口,那么这个JavaBean对象可以感知自己被绑定到Session中和从Session中删除的这两个操作,
测试代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page import="com.legend.listener.JavaBeanDemo1" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
// 将javabean对象绑定到session中
session.setAttribute("bean", new JavaBeanDemo1("legend"));
// 从session中删除javabean对象
session.removeAttribute("bean");
%>
</body>
</html>
输出结果:
legend被加到session中了
legend被session移除了
-
HttpSessionActivationListener接口
实现了HttpSessionActivationListener接口的JavaBean对象可以感知自己被活化(反序列化)和钝化(序列化)的事件
当绑定到HttpSession对象中的javabean对象将要随HttpSession对象被钝化(序列化)之前,web服务器调用该
javabean对象的void sessionWillPassivate(HttpSessionEvent event) 方法。这样javabean对象就可以知道
自己将要和HttpSession对象一起被序列化(钝化)到硬盘中.
当绑定到HttpSession对象中的javabean对象将要随HttpSession对象被活化(反序列化)之后,web服务器调用
该javabean对象的void sessionDidActive(HttpSessionEvent event)方法。这样javabean对象就可以知道自己
将要和 HttpSession对象一起被反序列化(活化)回到内存中
范例:
public class JavaBeanDemo2 implements HttpSessionActivationListener {
private static final long serialVersionUID = 7589841135210272124L;
private String name;
@Override
public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) {
System.out.println(name + "和session一起被序列化(钝化)到硬盘了,session的id是:"
+ httpSessionEvent.getSession().getId());
}
@Override
public void sessionDidActivate(HttpSessionEvent httpSessionEvent) {
System.out.println(name+"和session一起从硬盘反序列化(活化)回到内存了,session的id是:"
+ httpSessionEvent.getSession().getId());
}
public JavaBeanDemo2(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
为了观察绑定到HttpSession对象中的javabean对象随HttpSession对象一起被钝化到硬盘上和从硬盘上重新活化回到内存中的的过程,我们需要借助tomcat服务器帮助
我们完成HttpSession对象的钝化和活化过程,具体做法如下:
在WebRootMETA-INF文件夹下创建一个context.xml文件,如下所示:
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="gacl"/>
</Manager>
</Context>
在context.xml文件文件中配置了1分钟之后就将HttpSession对象钝化到本地硬盘的一个文件夹中。
jsp测试代码如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@page import="com.legend.listener.JavaBeanDemo2"%>
<!DOCTYPE HTML>
<html>
<head>
<title></title>
</head>
<body>
访问JSP页面,HttpSession就创建了,创建好的Session的Id是:${pageContext.session.id}<hr/>
<%
session.setAttribute("bean",new JavaBeanDemo2("legend"));
%>
</body>
</html>
访问这个jsp页面,服务器就会马上创建一个HttpSession对象,然后将实现了HttpSessionActivationListener接口的JavaBean对象绑定到session对象中,这个jsp页面在
等待1分钟之后没有人再次访问,那么服务器就会自动将这个HttpSession对象钝化(序列化)到硬盘上
当再次访问这个Jsp页面时,服务器又会自动将已经钝化(序列化)到硬盘上HttpSession对象重新活化(反序列化)回到内存中。
Listener常见应用
统计在线人数
在JavaWeb应用开发中,有时候我们需要统计当前在线的用户数,此时就可以使用监听器技术来实现这个功能了。
public class OnLineCountListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
ServletContext context =
httpSessionEvent.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if (onLineCount == null) {
context.setAttribute("onLineCount", 1);
}else {
onLineCount++;
context.setAttribute("onLineCount", onLineCount);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
ServletContext context = httpSessionEvent.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if (onLineCount == null) {
context.setAttribute("onLineCount", 1);
}else {
onLineCount--;
context.setAttribute("onLineCount", onLineCount);
}
}
}
自定义Session扫描器
当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的session销毁,那么此时也可以借助监听器技术来实现。
public class SessionScanerListener implements HttpSessionListener,ServletContextListener {
/**
* @Field: list
* 定义一个集合存储服务器创建的HttpSession
* LinkedList不是一个线程安全的集合
*/
/**
* private List<HttpSession> list = new LinkedList<HttpSession>();
* 这样写涉及到线程安全问题,SessionScanerListener对象在内存中只有一个
* sessionCreated可能会被多个人同时调用,
* 当有多个人并发访问站点时,服务器同时为这些并发访问的人创建session
* 那么sessionCreated方法在某一时刻内会被几个线程同时调用,几个线程并发调用sessionCreated方法
* sessionCreated方法的内部处理是往一个集合中添加创建好的session,那么在加session的时候就会
* 涉及到几个Session同时抢夺集合中一个位置的情况,所以往集合中添加session时,一定要保证集合是线程安全的才行
* 如何把一个集合做成线程安全的集合呢?
* 可以使用使用 Collections.synchronizedList(List<T> list)方法将不是线程安全的list集合包装线程安全的list集合
*/
//使用 Collections.synchronizedList(List<T> list)方法将LinkedList包装成一个线程安全的集合
private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());
//定义一个对象,让这个对象充当一把锁,用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
private Object lock = new Object();
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("session被创建了!!");
HttpSession session = se.getSession();
synchronized (lock){
/**
*将该操作加锁进行锁定,当有一个thread-1(线程1)在调用这段代码时,会先拿到lock这把锁,然后往集合中添加session,
*在添加session的这个过程中假设有另外一个thread-2(线程2)来访问了,thread-2可能是执行定时器任务的,
*当thread-2要调用run方法遍历list集合中的session时,结果发现遍历list集合中的session的那段代码被锁住了,
*而这把锁正在被往集合中添加session的那个thread-1占用着,因此thread-2只能等待thread-1操作完成之后才能够进行操作
*当thread-1添加完session之后,就把lock放开了,此时thread-2拿到lock,就可以执行遍历list集合中的session的那段代码了
*通过这把锁就保证了往集合中添加session和变量集合中的session这两步操作不能同时进行,必须按照先来后到的顺序来进行。
*/
list.add(session);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session被销毁了了!!");
}
/* Web应用启动时触发这个事件
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("web应用初始化");
//创建定时器
Timer timer = new Timer();
//每隔30秒就定时执行任务
timer.schedule(new MyTask(list,lock), 0, 1000*30);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("web应用关闭");
}
}
class MyTask extends TimerTask {
//存储HttpSession的list集合
private List<HttpSession> list;
//存储传递过来的锁
private Object lock;
public MyTask(List<HttpSession> list,Object lock){
this.list = list;
this.lock = lock;
}
/* run方法指明了任务要做的事情
* @see java.util.TimerTask#run()
*/
@Override
public void run() {
//将该操作加锁进行锁定
synchronized (lock) {
System.out.println("定时器执行!!");
ListIterator<HttpSession> it = list.listIterator();
/**
* 迭代list集合中的session,在迭代list集合中的session的过程中可能有别的用户来访问,
* 用户一访问,服务器就会为该用户创建一个session,此时就会调用sessionCreated往list集合中添加新的session,
* 然而定时器在定时执行扫描遍历list集合中的session时是无法知道正在遍历的list集合又添加的新的session进来了,
* 这样就导致了往list集合添加的新的session和遍历list集合中的session这两个操作无法达到同步
* 那么解决的办法就是把"list.add(session)和while(it.hasNext()){//迭代list集合}"这两段代码做成同步,
* 保证当有一个线程在访问"list.add(session)"这段代码时,另一个线程就不能访问"while(it.hasNext()){//迭代list集合}"这段代码
* 为了能够将这两段不相干的代码做成同步,只能定义一把锁(Object lock),然后给这两步操作加上同一把锁,
* 用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
* 当在执行往list集合添加的新的session操作时,就必须等添加完成之后才能够对list集合进行迭代操作,
* 当在执行对list集合进行迭代操作时,那么必须等到迭代操作结束之后才能够往往list集合添加的新的session
*/
while(it.hasNext()){
HttpSession session = (HttpSession) it.next();
/**
* 如果当前时间-session的最后访问时间>1000*15(15秒)
* session.getLastAccessedTime()获取session的最后访问时间
*/
if(System.currentTimeMillis()-session.getLastAccessedTime()>1000*30){
//手动销毁session
session.invalidate();
//移除集合中已经被销毁的session
it.remove();
}
}
}
}
}