監聽器

一、監聽器的概覽

監聽器是指專門用于對其他對象身上發生的事件或狀態的改變進行監聽和相應處理的對象,當被監視的對象發生變化時,立即采取相應的行動。比如統計用戶在線人數,監聽人就是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閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容