一、监听器的概览
监听器是指专门用于对其他对象身上发生的事件或状态的改变进行监听和相应处理的对象,当被监视的对象发生变化时,立即采取相应的行动。比如统计用户在线人数,监听人就是web应用服务器,被监听者就是session对象。
web监听器是Servlet规范中定义的一种特殊类,用于监听ServletContext、HttpSession和ServletRequest,可以在事件发生前、发生后做一些必要的处理。web监听器可以用于统计用户在线人数和在线用户、系统启动时加载初始化信息、统计网站访问量等。
二、监听器的分类
按监听对象划分
- 用于监听应用程序环境(ServletContext)的事件监听器
- 用于监听用户会话对象(HttpSession)的事件监听器
- 用于监听请求消息对象(ServletRequest)的事件监听器
按监听的事件划分
- 监听域对象自身的创建和销毁的事件监听器
- 监听域对象中的属性的增加和删除的事件监听器
- 监听绑定到HttpSession域中的某个对象的状态的事件监听器
三、监听器的启动顺序
四、监听器详解
监听器在web容器中的配置方式
①web.xml中配置监听器:
<web-app...>
...
<listener>
<listener-class>com.listener.testListener</listener-class>
</listener>
<context-param>
<param-name>initParam</param-name>
<param-value>zoyoto</param-value>
</context-param>
...
<web-app...>
Servelt容器先解析web.xml,获取Listener的值,通过反射生成监听器对象。
②在监听器类上标注@WebListener
@WebListener()
public class SomeSessionListener implements HttpSessionListener{...}
下面我们来看一下不同类别监听器的解析。
(1)监听域对象自身的创建和销毁的事件监听器
- ServletContext→ServletContextListener
- HttpSession→HttpSessionListener
- ServletRequest→ServletRequestListener
注意:上面三种监听器均要在web容器中注册
a.ServletContextListener
一个项目中只能定义一个ServletContext,但是一个ServletContext可以注册多个ServletContextListener。ServletContextListener是对ServeltContext的一个监听,在ServeltContext生成或销毁后才被调用。在方法里面调用event.getServletContext()可以获取ServletContext。
ServeltContext是一个上下文对象,它的数据供所有的应用程序共享,所以ServletContextListener的主要用途就是:做定时器和全局属性对象。下面我们看一下如何使用ServeltContext做定时器:
//ServletContextListener可以负责在应用服务器启动时打开定时器
@WebListener()
public class taskListener implements ServletContextListener{
private java.util.Timer timer = null;
//ServletContext创建时调用
@Override
public void contextDestroyed(ServletContextEvent event){
timer = new java.util.Timer(true);
event.getServletContext().log("定时器已启动");
//schedule(TimerTask task, long delay, long period)方法设定指定任务task在指定延迟delay后进行固定延迟peroid的执行
timer.schedule(new MyTask(event.getServletContext()), 0, 60*60*1000);
event.getServletContext().log("已经添加任务调度表");
}
//ServletContext销毁时调用
@Override
public void contextInitialized(ServletContextEvent servletContextEvent){
timer.cancel();
event.getServletContext().log("定时器销毁");
}
}
//定时任务
public class MyTask extends TimerTask{
private ServletContext context = null;
private static boolean isRunning = false;
private static final int STATISTICS_SCHEDULE_HOUR = 0;
public MyTask(ServletContext context){
this.context=context;
}
@Override
public void run(){
Calendar cal = Calendar.getInstance();
if (!isRunning) {
//查看是否为凌晨
if (STATISTICS_SCHEDULE_HOUR == cal.get(Calendar.HOUR_OF_DAY)){
isRunning = true;
context.log("开始执行指定任务");
//TODO自定义添加任务详情
executeTask();
//指定任务执行结束
isRunning = false;
context.log("指定任务执行结束");
}
}else{
context.log("上一次任务执行还未结束");
}
}
}
b.HttpSessionListener
一个HttpSession可以注册多个HttpSessionListener。HttpSessionListener是对Session的监听,在Session生成或销毁后被调用。HttpSessionListener主要用途是统计在线人数和记录访问日志。下面我们来看一下如何用该监听器统计在线人数:
@WebListener()
public class testListener implements HttpSessionListener{
private static Integer userNumber = 0;
public static int getCount(){
return userNumber;
}
//session创建时调用
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent){
System.out.println("sessionCreated");
userNumber++:
}
//session销毁时调用
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent){
System.out.println("sessionDestroyed");
userNumber--:
}
}
启动服务器,然后在浏览器输入URL,就会发现控制台输出了sessionCreated;这时我们关掉浏览器,session并不会立刻就销毁,而是隔一段时间才销毁。我们可以在web.xml中设置session-timeout,假如session-timeout为0,就说明这个session没有超时的限制。我们设置为1,意思就是等一分钟,如果session没有点击的话,就会被销毁,就会打印出sessionDestroyed。
<session-config>
<session-timeout>1</session-timeout>
</session-config>
在HttpSessionListener监听器里面,我们可以用httpSessionEvent.getSession().getServletContext()
得到上下文,因为httpSessionEvent中只有getSession(),没有getServletcontext()。
c.ServletRequestListener
一个ServletRequest可以注册多个ServletRequestListener。ServletRequestListener接口用于监听Web应用程序中ServletRequest对象的创建和销毁。ServletRequestListener主要用途是读取参数和记录访问历史。
@WebListener()
public class testListener implements ServletRequestListener{
//request创建时(发送请求)调用
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent){
System.out.println("requestCreated");
}
//request处理完毕销毁时调用
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent){
System.out.println("requestDestroyed");
}
}
此监听器可以启动Tomcat发送请求进行测试。
(2)监听域对象中的属性的增加和删除的事件监听器
- ServletContext→ServletContextAttributeListener
- HttpSession→HttpSessionAttributeListener
- ServletRequest→ServletRequestAttributeListener
注意:上面三种监听器均要在web容器中注册
a.ServletContextAttributeListener
此接口用于监听ServletContext属性的变化。
@WebListener()
public class testListener implements ServletContextAttributeListener{
//添加属性
@Override
public void attributeAdded(ServletContextAttributeEvent e){
System.out.println("attributeAdded"+e.getName());
}
//删除属性
@Override
public void attributeRemoved(ServletContextAttributeEvent e){
System.out.println("attributeRemoved"+e.getName());
}
//修改属性
@Override
public void attributeReplaced(ServletContextAttributeEvent e){
System.out.println("attributeReplaced"+e.getName());
}
}
b.HttpSessionAttributeListener
此接口用于监听Session属性的变化。
@WebListener()
public class testListener implements HttpSessionAttributeListener{
//session.setAttribute()
@Override
public void attributeAdded(HttpSessionBindingEvent e){
System.out.println("attributeAdded"+e.getName());
}
//session.removeAttribute()
@Override
public void attributeRemoved(HttpSessionBindingEvent e){
System.out.println("attributeRemoved"+e.getName());
}
//session.replaceAttribute()
@Override
public void attributeReplaced(HttpSessionBindingEvent e){
System.out.println("attributeReplaced"+e.getName());
}
}
c.ServletRequestAttributeListener
此接口用于监听request属性的变化。
@WebListener()
public class testListener implements ServletRequestAttributeListener{
//request.setAttribute()
@Override
public void attributeAdded(HttpSessionBindingEvent e){
System.out.println("attributeAdded"+e.getName());
}
//request.removeAttribute();
@Override
public void attributeRemoved(HttpSessionBindingEvent e){
System.out.println("attributeRemoved"+e.getName());
}
//对已经存在于request中的属性再次调用request.setAttribute("user", "bbb")
//如request.setAttribute("user", "aaa");request.setAttribute("user", "bbb");
@Override
public void attributeReplaced(HttpSessionBindingEvent e){
System.out.println("attributeReplaced"+e.getName());
}
}
(3)监听绑定到HttpSession域中的某个对象的状态的事件监听器
HttpSession中的对象状态:①绑定→解除绑定 ②钝化→活化
3.1钝化与活化
钝化:将session对象持久化到存储设备上;
活化:将session从存储设备上恢复
Session正常是放到服务器内存当中的,服务器会对每一个在线用户创建一个Session对象,如果当前用户很多,Session内存的开销是非常大的,影响性能。而Session的钝化机制可以解决这个问题。
钝化的本质就是把一部分比较长时间没有变动的Session暂时序列化到系统文件或者数据库系统当中,等该Session的用户重新使用的时候,服务器在内存中找不到Session,就会到磁盘中去找,然后把Session反序列化到内存中。整个过程由服务器自动完成。
注意,Session只有可以被序列化才能被钝化。Session序列化时,服务器会把Session保存到硬盘中,以sessionID命名,以“.session”作为扩展名。
Session钝化机制由SessionManager管理,Tomcat有两种Session管理器:
①StandardManager(标准会话管理器)
<Manager className="org.apache.catalina.session.StandardManager"
maxInactiveInterval="7200"/>
- Tomcat6的默认会话管理器,用于非集群环境中对单个处于运行状态的Tomcat实例会话进行管理。
- 当运行Tomcat时,StandardManager实例负责在内存中管理Session;但当服务器关闭时,会将当前内存中的Session写入到磁盘上的一个名叫SESSION.ser的文件,等服务器再次启动时,会重新载入这些Session。
- 另一种情况是Web应用程序被重新加载时,内存中的Session对象也会被钝化到服务器的文件系统中。
- 钝化后的文件默认被保存在Tomcat的安装路径
$CATALINA_HOME/work/Catalina/<hostname>/<webapp-name>/
下的SESSIONS.ser
文件中。
②PersistentManager(持久会话管理器)
- PersistentManager和StandardManager的区别在于PersistentManager自己实现了Store类,使Session可以被保存到不同地方(Database,Redis等),而不局限于只保存在SESSION.ser文件中。Store表示了管理session对的二级存储设备。
<ManagerclassName="org.apache.catalina.session.PersistentManager"
debug="0"
<!--当Tomcat正常停止及重启动时,是否要储存及重载会话。-->
saveOnRestart="true"
<!--可容许现行最大会话的最大数,-1代表无限制-->
maxActiveSession="-1"
<!--在调换会话至磁盘之前,此会话必须闲置的最小时间-->
minIdleSwap="0"
<!--在文件交换之前,此会话可以闲置的最大时间(以秒为单位)。-1表示会话不应该被强迫调换至文件。-->
maxIdleSwap="0"
<!--在备份之前,此会话必须闲置的最大时间。-1表示不进行备份-->
maxIdleBackup="-1"
<!--保存在Reids中-->
<Store className="com.cdai.test.RedisStore" host="192.168.1.1"port="6379"/>
</Manager>
- PersistentManager支持两种钝化驱动类:org.apache.Catalina.FileStore和org.apache.Catalina.JDBCStore,分别支持将会话保存至文件存储(FileStore)或JDBC存储(JDBCStore)中。
<!--保存到文件中-->
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<!--每个用户的会话会被保存至directory指定的目录中的文件中-->
<Store className="org.apache.catalina.session.FileStore"
directory="/data/tomcat-sessions"/>
</Manager>
<!--保存到JDBCStore中-->
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<Store className="org.apache.catalina.session.JDBCStore"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mydb?user=root;password=123"/>
</Manager>
- 在持久化管理器中,session可以被备份或换出到存储器中。
- 换出:当内存中有过多的session对象时,持久化管理器会直接将session对象换出,直到当前活动对象等于maxActiveSession指定的值。
- 备份:不是所有的session都会备份,PersistentManager只会将那些空闲时间超过maxIdleBackup的session备份到文件或数据库中。该任务由processMaxIdleBackups方法完成。
- 此时Tomcat只是在下面三种情况会将Session通过Store保存起来。
- 当Session的空闲时间超过minIdleSwap和maxIdleSwap时,会将Session换出
- 当Session的空闲时间超过maxIdleBackup时,会将Session备份出去
- 当Session总数大于maxActiveSession时,会将超出部分的空闲Session换出
3.2接口规范
servlet规范中提供的两种接口用以监听session内的对象:
HttpSessionBindingListener HttpSessionActivationListener
| |
/ \ / \
valueBound valueUnBound sessionWillPassivate sessionDidActivate
(绑定) (解除绑定) (钝化) (活化)
以上接口皆不需要在web容器中注册。
(1)绑定和解绑接口
当对象被放到session里执行或从session里移除就会执行HttpSessionBindingListener内的方法,但是对象必须实现该Listener接口。需要注意的是,我们创建的这个类并不是创建一个监听器,而是创建一个被监听器绑定的Javabean。
public class User implements HttpSessionBindingListener{
private String userName;
private String password;
//对象被放进session时触发,如session.setAttribute("user",user);
@Override
public void valueBound(HttpSessionBindingEvent e){
//getName()方法可以取得属性设定或移除时指定的名称
System.out.println(this + "被绑定到session \"" + e.getSession.getId() + "\"的" +e.getName()+ "属性上);
}
//从session移除后触发,
@Override
public void valueUnBound(HttpSessionBindingEvent e){
System.out.println(this + "被从session \"" + e.getSession.getId() + "\"的" +e.getName()+ "属性上移除);
}
//此处省略getter和setter
}
valueUnbound方法将被以下任一条件触发:
a. 执行session.setAttribute("uocl", 非uocl对象) 时。
b. 执行session.removeAttribute("uocl") 时。
c. 执行session.invalidate()时。
d. session超时后。
(2)钝化和活化接口
服务器内存对session进行钝化或者活化时你会收到监听事件。什么时候序列化和反序列化完全由容器决定,我们只能用HttpSessionActivationListener接口监听器监听对象是否被钝化。
//要注意只有实现Serializable接口才能被钝化
public class User implements HttpSessionActivationListener,Serializable{
private String userName;
private String password;
//被钝化时调用
@Override
public void sessionWillPassivate(HttpSessionEvent e){
System.out.println(this + "即将保存到硬盘。sessionId: " + e.getSession.getId());
}
//被活化时调用
@Override
public void sessionDidActivate(HttpSessionEvent e){
System.out.println(this + "已经成功从硬盘中加载。sessionId: " + e.getSession.getId());
}
//此处省略getter和setter
}
测试的时候先把Tomcat关掉,就会发现控制台输出了
sessionWillPassivate org.apache.catalina.session.StandardSessionFacade@4f2d26d2
也就是session已经被钝化了,此时在Tomcat安装路径下会发现SESSION.er文件。然后我们再来重启Tomcat,发现控制台输出:
sessionDidActivate org.apache.catalina.session.StandardSessionFacade@4f2d26d2
此时会发现在Tomcat安装路径下的SESSION.er文件消失了,也就说明session已经被活化了。
同样的,如果我们设定了maxIdleeSwap="1",当用户开着浏览器一分钟不操作页面的话服务器就会将session钝化,将session生成文件放在tomcat工作目录下,直到再次访问才会被激活。