监听器

一、监听器的概览

监听器是指专门用于对其他对象身上发生的事件或状态的改变进行监听和相应处理的对象,当被监视的对象发生变化时,立即采取相应的行动。比如统计用户在线人数,监听人就是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工作目录下,直到再次访问才会被激活。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容