Java Web 之 Listener

本文包括:

1、Listener簡(jiǎn)介

2、Servlet監(jiān)聽器

3、監(jiān)聽三個(gè)域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器

4、監(jiān)聽三個(gè)域?qū)ο蟮膶傩?Attribute)的變化的事件監(jiān)聽器

5、監(jiān)聽綁定到 HttpSession 域中的某個(gè)對(duì)象的狀態(tài)的事件監(jiān)聽器

1、Listener簡(jiǎn)介

  • Listener(監(jiān)聽器)就是一個(gè)實(shí)現(xiàn)特定接口的普通java程序,這個(gè)程序?qū)iT用于監(jiān)聽另一個(gè)java對(duì)象的方法調(diào)用或?qū)傩愿淖?,?dāng)被監(jiān)聽對(duì)象發(fā)生上述事件后,監(jiān)聽器某個(gè)方法將立即被執(zhí)行。

  • 為了加深理解,自定義監(jiān)聽器來(lái)練練手,假設(shè)現(xiàn)在有個(gè)體重100的人要吃飯了,要監(jiān)聽他吃飯的動(dòng)作,捕捉到了之后再打印它的體重,具體思路如下;

    1. 事件源類:

       public class Person {
           private String name;
           private int weight;// 體重
           public String getName() {
               return name;
           }
       
           public void setName(String name) {
               this.name = name;
           }
       
           public int getWeight() {
               return weight;
           }
       
           public void setWeight(int weight) {
               this.weight = weight;
           }
       
       }
      
    2. 監(jiān)聽器接口:

       public interface PersonListener {
           public void personeating(PersonEvent event);// 監(jiān)聽方法,需要一個(gè)事件對(duì)象作為參數(shù)
       }
      
    3. 事件類:

       public class PersonEvent {
           private Object source;// 事件源
       
           public Object getSource() {
               return source;
           }
       
           public void setSource(Object source) {
               this.source = source;
           }
       
           // 提供一個(gè)這樣的構(gòu)造方法:構(gòu)造事件對(duì)象時(shí),接收事件源(Person)
           public PersonEvent(Person person) {
               this.source = person;
           }
       
       }
      
    4. 在事件源中注冊(cè)監(jiān)聽器:

       public class Person {
           private String name;
           private int weight;// 體重
           private PersonListener listener;
       
           // 注冊(cè)監(jiān)聽器
           public void addPersonListener(PersonListener listener) {
               this.listener = listener;
           }
           public String getName() {
               return name;
           }
       
           public void setName(String name) {
               this.name = name;
           }
       
           public int getWeight() {
               return weight;
           }
       
           public void setWeight(int weight) {
               this.weight = weight;
           }
       
       }
      
    5. 操作事件源 ----- 在事件源方法中,構(gòu)造事件對(duì)象,參數(shù)為當(dāng)前事件源(this),傳遞事件對(duì)象給監(jiān)聽器的監(jiān)聽方法:

       public class Person {
           private String name;
           private int weight;// 體重
           private PersonListener listener;
       
           // 注冊(cè)監(jiān)聽器
           public void addPersonListener(PersonListener listener) {
               this.listener = listener;
           }
       
           // 吃飯
           public void eat() {
               // 體重增加
               weight += 5;
               // 調(diào)用監(jiān)聽器監(jiān)聽方法
               if (listener != null) {
                   // 監(jiān)聽器存在
                   // 創(chuàng)建事件對(duì)象 --- 通過(guò)事件對(duì)象可以獲得事件源
                   PersonEvent event = new PersonEvent(this);
       
                   listener.personeating(event);
               }
           }
       
           public String getName() {
               return name;
           }
       
           public void setName(String name) {
               this.name = name;
           }
       
           public int getWeight() {
               return weight;
           }
       
           public void setWeight(int weight) {
               this.weight = weight;
           }
       
       }
      
    6. 測(cè)試:

       public class PersonTest {
           public static void main(String[] args) {
               // 步驟一 創(chuàng)建事件源
               Person person = new Person();
               person.setName("小明");
               person.setWeight(100);
       
               // 步驟二 給事件源注冊(cè)監(jiān)聽器(該監(jiān)聽器由匿名內(nèi)部類創(chuàng)建)
               person.addPersonListener(new PersonListener() {
                   @Override
                   public void personeating(PersonEvent event) {
                       System.out.println("監(jiān)聽到了,人正在吃飯!");
                       
                       // 在監(jiān)聽方法中可以獲得事件源對(duì)象,進(jìn)而可以操作事件源對(duì)象
                       Person person = (Person) event.getSource();
                       System.out.println(person.getName());
                       System.out.println(person.getWeight());
                   }
               });
       
               // 步驟三 操作事件源
               person.eat();// 結(jié)果監(jiān)聽方法被調(diào)用
           }
       }
      

2、Servlet監(jiān)聽器

  • 在Servlet規(guī)范中定義了多種類型的監(jiān)聽器,它們用于監(jiān)聽的事件源是三個(gè)域?qū)ο螅謩e為:

    • ServletContext

    • HttpSession

    • ServletRequest

  • Servlet規(guī)范針對(duì)這三個(gè)域?qū)ο笊系牟僮?,又把這多種類型的監(jiān)聽器劃分為三種類型:

    • 監(jiān)聽三個(gè)域?qū)ο蟮膭?chuàng)建和銷毀的事件監(jiān)聽器

    • 監(jiān)聽三個(gè)域?qū)ο蟮膶傩?Attribute)的增加和刪除的事件監(jiān)聽器

    • 監(jiān)聽綁定到 HttpSession 域中的某個(gè)對(duì)象的狀態(tài)的事件監(jiān)聽器。(查看API文檔)

  • 編寫 Servlet 監(jiān)聽器:

    • 和編寫其它事件監(jiān)聽器一樣,編寫Servlet監(jiān)聽器也需要實(shí)現(xiàn)一個(gè)特定的接口,并針對(duì)相應(yīng)動(dòng)作覆蓋接口中的相應(yīng)方法。

    • 和其它事件監(jiān)聽器略有不同的是,Servlet監(jiān)聽器的注冊(cè)不是直接注冊(cè)在事件源上,而是由WEB容器負(fù)責(zé)注冊(cè),開發(fā)人員只需在web.xml文件中使用<listener>標(biāo)簽配置好監(jiān)聽器,web容器就會(huì)自動(dòng)把監(jiān)聽器注冊(cè)到事件源中。

    • 一個(gè) web.xml 文件中可以配置多個(gè) Servlet 事件監(jiān)聽器,web 服務(wù)器按照它們?cè)?web.xml 文件中的注冊(cè)順序來(lái)加載和注冊(cè)這些 Serlvet 事件監(jiān)聽器。配置代碼如下所示:

        <!-- 對(duì)監(jiān)聽器進(jìn)行注冊(cè) -->
        <!-- 監(jiān)聽器和Servlet、Filter不同,不需要url配置,監(jiān)聽器執(zhí)行不是由用戶訪問(wèn)的,監(jiān)聽器 是由事件源自動(dòng)調(diào)用的 -->
        <listener>
            <listener-class>cn.itcast.servlet.listener.MyServletContextListener</listener-class>
        </listener>
      

3、監(jiān)聽三個(gè)域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器

3.1、ServletContextListener

  • ServletContextListener 接口用于監(jiān)聽 ServletContext 對(duì)象的創(chuàng)建和銷毀事件。

    • 當(dāng) ServletContext 對(duì)象被創(chuàng)建時(shí),調(diào)用接口中的方法:

        ServletContextListener.contextInitialized (ServletContextEvent sce)
      
    • 當(dāng) ServletContext 對(duì)象被銷毀時(shí),調(diào)用接口中的方法:

        ServletContextListener.contextDestroyed(ServletContextEvent sce)
      
  • ServletContext域?qū)ο蠛螘r(shí)創(chuàng)建和銷毀:

    • 創(chuàng)建:服務(wù)器啟動(dòng)時(shí),針對(duì)每一個(gè)web應(yīng)用創(chuàng)建Servletcontext

    • 銷毀:服務(wù)器關(guān)閉前,先關(guān)閉代表每一個(gè)web應(yīng)用的ServletContext

  • ServletContext主要用來(lái)干什么?

    1. 保存全局應(yīng)用數(shù)據(jù)對(duì)象

      • 在服務(wù)器啟動(dòng)時(shí),對(duì)一些對(duì)象進(jìn)行初始化,并且將對(duì)象保存ServletContext數(shù)據(jù)范圍內(nèi) —— 實(shí)現(xiàn)全局?jǐn)?shù)據(jù)

      • 例如:創(chuàng)建數(shù)據(jù)庫(kù)連接池

    2. 加載框架配置文件

      • Spring框架(配置文件隨服務(wù)器啟動(dòng)加載) org.springframework.web.context.ContextLoaderListener
    3. 實(shí)現(xiàn)任務(wù)調(diào)度(定時(shí)器),啟動(dòng)定時(shí)程序

      • java.util.Timer:一種線程設(shè)施,用于安排以后在后臺(tái)線程中執(zhí)行的任務(wù),可安排任務(wù)執(zhí)行一次,或者定期重復(fù)執(zhí)行。

      • Timer提供了啟動(dòng)定時(shí)任務(wù)方法 Timer.schedule(),其中有兩種方法需要記?。?/p>

        1. 在指定的一個(gè)時(shí)間時(shí)啟動(dòng)定時(shí)器,定期執(zhí)行一次

            Timer.schedule(TimerTask task, Date firstTime, long period)  
          
        2. 在當(dāng)前時(shí)間延遲多少毫秒后啟動(dòng)定時(shí)器,定期執(zhí)行一次

            Timer.schedule(TimerTask task, long delay, long period)  
          
      • 停止定時(shí)器,取消任務(wù)

          Timer.cancel() 
        
  • demo:

      package cn.itcast.servlet.listener;
      
      import java.text.DateFormat;
      import java.text.ParseException;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.Timer;
      import java.util.TimerTask;
      
      import javax.servlet.ServletContext;
      import javax.servlet.ServletContextEvent;
      import javax.servlet.ServletContextListener;
      
      /**
       * 自定義 Context監(jiān)聽器
       * 
       * @author seawind
       * 
       */
      public class MyServletContextListener implements ServletContextListener {
      
          @Override
          public void contextDestroyed(ServletContextEvent sce) {
              System.out.println("監(jiān)聽ServletContext對(duì)象銷毀了...");
          }
      
          @Override
          public void contextInitialized(ServletContextEvent sce) {
              System.out.println("監(jiān)聽ServletContext對(duì)象創(chuàng)建了...");
              // 獲得事件源
              ServletContext servletContext = sce.getServletContext();
              // 向ServletContext 中保存數(shù)據(jù)
      
              // 啟動(dòng)定時(shí)器
              final Timer timer = new Timer();
              // 啟動(dòng)定時(shí)任務(wù)
      
              // timer.schedule(new TimerTask() {
              // @Override
              // // 這就是一個(gè)線程
              // public void run() {
              // System.out.println("定時(shí)器執(zhí)行了...");
              // }
              // }, 0, 3000); // 馬上啟動(dòng) 每隔3秒重復(fù)執(zhí)行
      
              // 指定時(shí)間啟動(dòng)定時(shí)器
              DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
              try {
                  Date first = dateFormat.parse("2012-08-07 10:42:00");
                  timer.schedule(new TimerTask() {
                      int i;
      
                      @Override
                      public void run() {
                          i++;
                          System.out.println("從10點(diǎn)40分開始啟動(dòng)程序,每隔3秒重復(fù)執(zhí)行");
                          if (i == 10) {
                              timer.cancel();// 取消定時(shí)器任務(wù)
                          }
                      }
                  }, first, 3000);
              } catch (ParseException e) {
                  e.printStackTrace();
              }
          }
      
      }
    

3.2、HttpSessionListener

  • HttpSessionListener接口用于監(jiān)聽HttpSession的創(chuàng)建和銷毀

    • 創(chuàng)建一個(gè)Session時(shí),接口中的該方法將會(huì)被調(diào)用:

        HttpSessionListener.sessionCreated(HttpSessionEvent se) 
      
    • 銷毀一個(gè)Session時(shí),接口中的該方法將會(huì)被調(diào)用:

        HttpSessionListener.sessionDestroyed (HttpSessionEvent se) 
      
  • Session域?qū)ο髣?chuàng)建和銷毀:

    • 創(chuàng)建:瀏覽器訪問(wèn)服務(wù)器時(shí),服務(wù)器為每個(gè)瀏覽器創(chuàng)建不同的 session 對(duì)象,服務(wù)器創(chuàng)建session

    • 銷毀:如果用戶的session的30分鐘沒(méi)有使用,session就會(huì)過(guò)期,我們?cè)赥omcat的web.xml里面也可以配置session過(guò)期時(shí)間。

  • demo:

      package cn.itcast.servlet.listener;
      
      import javax.servlet.http.HttpSession;
      import javax.servlet.http.HttpSessionEvent;
      import javax.servlet.http.HttpSessionListener;
      
      public class MyHttpSessionListener implements HttpSessionListener {
      
          @Override
          public void sessionCreated(HttpSessionEvent se) {
              // 通過(guò)事件對(duì)象獲得session 的id 
              System.out.println("session被創(chuàng)建了");
              HttpSession session = se.getSession();
              System.out.println("id:" + session.getId());
          }
      
          @Override
          public void sessionDestroyed(HttpSessionEvent se) {
              System.out.println("session被銷毀了");
              HttpSession session = se.getSession();
              System.out.println("id:" + session.getId());
          }
      
      }
    

關(guān)于HttpSession的生命周期、具體描述詳見:JSP 會(huì)話管理

3.3、ServletRequestListener(很少用)

  • ServletRequestListener 接口用于監(jiān)聽ServletRequest 對(duì)象的創(chuàng)建和銷毀:

    • ServletRequest對(duì)象被創(chuàng)建時(shí),監(jiān)聽器的requestInitialized方法將會(huì)被調(diào)用。

    • ServletRequest對(duì)象被銷毀時(shí),監(jiān)聽器的requestDestroyed方法將會(huì)被調(diào)用。

  • ServletRequest域?qū)ο髣?chuàng)建和銷毀的時(shí)機(jī):

    • 創(chuàng)建:用戶每一次訪問(wèn),都會(huì)創(chuàng)建一個(gè)reqeust

    • 銷毀:當(dāng)前訪問(wèn)結(jié)束,request對(duì)象就會(huì)銷毀

  • 這個(gè)監(jiān)聽器最需要注意的:

    • 使用forward ---- request創(chuàng)建銷毀一次 (因?yàn)檗D(zhuǎn)發(fā)本質(zhì)是一次請(qǐng)求)

    • 使用sendRedirect ---- request創(chuàng)建銷毀兩次 (因?yàn)橹囟ㄏ虮举|(zhì)是兩次請(qǐng)求)

關(guān)于ServletRequest詳見:http://www.itdecent.cn/p/7e2e3fd58e91

3.4、案例:統(tǒng)計(jì)在線人數(shù)

  • 圖解:

  • 首先,初始化在線人數(shù),根據(jù)前文,ServletContextListener可以監(jiān)聽ServletContext對(duì)象的創(chuàng)建,所以新建一個(gè)實(shí)現(xiàn)監(jiān)聽器接口的類:

      package cn.itcast.servlet.listener.demo2;
      
      import javax.servlet.ServletContext;
      import javax.servlet.ServletContextEvent;
      import javax.servlet.ServletContextListener;
      
      public class OnlineCountServletContextListener implements
              ServletContextListener {
      
          @Override
          public void contextDestroyed(ServletContextEvent sce) {
          }
      
          @Override
          public void contextInitialized(ServletContextEvent sce) {
              // 初始化在線人數(shù)為0
              ServletContext context = sce.getServletContext();
              context.setAttribute("onlinenum", 0);
          }
      
      }
    
  • 利用HttpSessionListener監(jiān)聽HttpSession對(duì)象的創(chuàng)建和銷毀,可以統(tǒng)計(jì)在線人數(shù):

      package cn.itcast.servlet.listener.demo2;
      
      import javax.servlet.ServletContext;
      import javax.servlet.http.HttpSession;
      import javax.servlet.http.HttpSessionEvent;
      import javax.servlet.http.HttpSessionListener;
      
      public class OnlineCountHttpSessionListener implements HttpSessionListener {
      
          @Override
          public void sessionCreated(HttpSessionEvent se) {
              // 當(dāng)Session對(duì)象被創(chuàng)建時(shí),在線人數(shù) +1
              HttpSession session = se.getSession();
              ServletContext context = session.getServletContext();
      
              int onlinenum = (Integer) context.getAttribute("onlinenum");
              context.setAttribute("onlinenum", onlinenum + 1);
      
              System.out.println(session.getId() + "被創(chuàng)建了...");
          }
      
          @Override
          public void sessionDestroyed(HttpSessionEvent se) {
              // 當(dāng)Session對(duì)象被銷毀時(shí),在線人數(shù) - 1
              HttpSession session = se.getSession();
              ServletContext context = session.getServletContext();
      
              int onlinenum = (Integer) context.getAttribute("onlinenum");
              context.setAttribute("onlinenum", onlinenum - 1);
      
              System.out.println(session.getId() + "被銷毀了 ...");
          }
      
      }
    
  • 別忘了在web.xml中配置:

      <listener>
          <listener-class>cn.itcast.servlet.listener.demo2.OnlineCountServletContextListener</listener-class>
      </listener>
      <listener>
          <listener-class>cn.itcast.servlet.listener.demo2.OnlineCountHttpSessionListener</listener-class>
      </listener>
    
  • 簡(jiǎn)單創(chuàng)建一個(gè)JSP頁(yè)面(注意,JSP作用域中applicationScope的范圍是整個(gè)服務(wù)器,所以可以得到ServletContext對(duì)象中存儲(chǔ)的值):

      <%@ page language="java" contentType="text/html; charset=UTF-8"
          pageEncoding="UTF-8"%>
      <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
      </head>
      <body>
      <h1>顯示在線人數(shù)</h1>
      ${applicationScope.onlinenum }
      </body>
      </html>
    

3.5、案例:利用定時(shí)器定時(shí)銷毀Session

  • 圖解:

  • ScannerServletContextListener干了兩件事:初始化List<HttpSession>;啟動(dòng)定時(shí)器,每隔20秒執(zhí)行一次。

    注意:

    因?yàn)橐獜腖ist中刪除元素,所以循環(huán)用Iterator而不用foreach。

    在使用掃描刪除Session對(duì)象時(shí),要保證Session的List集合長(zhǎng)度不能改變(即此時(shí)此刻不能添加新的Session) ---- 利用同步解決 synchronized

      package cn.itcast.servlet.listener.demo3;
      
      import java.util.ArrayList;
      import java.util.Iterator;
      import java.util.List;
      import java.util.Timer;
      import java.util.TimerTask;
      
      import javax.servlet.ServletContext;
      import javax.servlet.ServletContextEvent;
      import javax.servlet.ServletContextListener;
      import javax.servlet.http.HttpSession;
      
      public class ScannerServletContextListener implements ServletContextListener {
      
          @Override
          public void contextDestroyed(ServletContextEvent sce) {
          }
      
          @Override
          public void contextInitialized(ServletContextEvent sce) {
              // 第一件事,創(chuàng)建Session的List集合
              final List<HttpSession> sessionList = new ArrayList<HttpSession>();
              // 將集合保存ServletContext對(duì)象
              ServletContext servletContext = sce.getServletContext();
              servletContext.setAttribute("sessionList", sessionList);
      
              // 第二件事,啟動(dòng)定時(shí)器,每隔20秒執(zhí)行一次
              Timer timer = new Timer();
              timer.schedule(new TimerTask() {
                  @Override
                  public void run() {
                      System.out.println("定時(shí)session掃描器執(zhí)行了....");
                      // 掃描Session的List集合,看哪個(gè)Session已經(jīng)1分鐘沒(méi)用了
                      // 發(fā)現(xiàn)Session1分鐘沒(méi)有使用,銷毀Session 從集合移除
                      synchronized (sessionList) {
                          Iterator<HttpSession> iterator = sessionList.iterator();
                          while (iterator.hasNext()) {
                              HttpSession session = iterator.next();
                              if (System.currentTimeMillis()
                                      - session.getLastAccessedTime() > 1000 * 60) {
                                  System.out.println(session.getId()
                                          + "對(duì)象已經(jīng)1分鐘沒(méi)有使用,被銷毀了...");
                                  // 銷毀Session
                                  session.invalidate();
                                  // 從集合移除Session
                                  iterator.remove();
      
                              }
                          }
                      }
                  }
              }, 0, 20000);
          }
      
      }
    
  • ScannerHttpSessionListener:

      package cn.itcast.servlet.listener.demo3;
      
      import java.util.List;
      
      import javax.servlet.ServletContext;
      import javax.servlet.http.HttpSession;
      import javax.servlet.http.HttpSessionEvent;
      import javax.servlet.http.HttpSessionListener;
      
      public class ScannerHttpSessionListener implements HttpSessionListener {
      
          @Override
          public void sessionCreated(HttpSessionEvent se) {
              // 在創(chuàng)建Session對(duì)象時(shí),將Session對(duì)象加入集合
              HttpSession httpSession = se.getSession();
              ServletContext context = httpSession.getServletContext();
      
              List<HttpSession> sessionList = (List<HttpSession>) context
                      .getAttribute("sessionList");
              synchronized (sessionList) {
                  sessionList.add(httpSession);
              }
      
              // 還是否需要context.setAttribute? ---- 不需要:因?yàn)橹暗玫降氖荓ist的地址
              context.setAttribute("sessionList", sessionList); // 這行代碼寫不寫無(wú)所謂
      
              System.out.println(httpSession.getId() + "被創(chuàng)建了...");
          }
      
          @Override
          public void sessionDestroyed(HttpSessionEvent se) {
          }
      
      }
    
  • 配置:

      <listener>
          <listener-class>cn.itcast.servlet.listener.demo3.ScannerServletContextListener</listener-class>
      </listener>
      <listener>
          <listener-class>cn.itcast.servlet.listener.demo3.ScannerHttpSessionListener</listener-class>
      </listener>
    

4、監(jiān)聽三個(gè)域?qū)ο蟮膶傩?Attribute)的變化的事件監(jiān)聽器

  • Servlet規(guī)范定義了監(jiān)聽 ServletContext, HttpSession, HttpServletRequest 這三個(gè)對(duì)象中的屬性(Attribute)變更信息事件的監(jiān)聽器。

  • 這三個(gè)監(jiān)聽器接口分別是

    • ServletContextAttributeListener

    • HttpSessionAttributeListener

    • ServletRequestAttributeListener

  • 這三個(gè)接口中都定義了三個(gè)方法來(lái)處理被監(jiān)聽對(duì)象中的屬性的增加,刪除和替換的事件,同一個(gè)事件在這三個(gè)接口中對(duì)應(yīng)的方法名稱完全相同,只是接受的參數(shù)類型不同

  • XXListener.attributeAdded(XXEvent)

    • 當(dāng)向被監(jiān)聽器對(duì)象中增加一個(gè)屬性時(shí),web容器就調(diào)用事件監(jiān)聽器的 attributeAdded 方法進(jìn)行相應(yīng),這個(gè)方法接受一個(gè)事件類型的參數(shù),監(jiān)聽器可以通過(guò)這個(gè)參數(shù)來(lái)獲得正在增加屬性的域?qū)ο蠛捅槐4娴接蛑械膶傩詫?duì)象

    • 各個(gè)域?qū)傩员O(jiān)聽器中的完整語(yǔ)法定義為:

        public void attributeAdded(ServletContextAttributeEvent scae)
        public void attributeAdded (HttpSessionBindingEvent  hsbe) 
        public void attributeAdded(ServletRequestAttributeEvent srae)
      
  • XXListener.attributeRemoved(XXEvent)

    • 當(dāng)刪除被監(jiān)聽對(duì)象中的一個(gè)屬性時(shí),web 容器調(diào)用事件監(jiān)聽器的這個(gè)方法進(jìn)行相應(yīng)

    • 各個(gè)域?qū)傩员O(jiān)聽器中的完整語(yǔ)法定義為:

        public void attributeRemoved(ServletContextAttributeEvent scae) 
        public void attributeRemoved (HttpSessionBindingEvent  hsbe) 
        public void attributeRemoved (ServletRequestAttributeEvent srae)
      
  • XXListener.attributeReplaced(XXEvent)

    • 當(dāng)監(jiān)聽器的域?qū)ο笾械哪硞€(gè)屬性被替換時(shí),web容器調(diào)用事件監(jiān)聽器的這個(gè)方法進(jìn)行相應(yīng)

    • 各個(gè)域?qū)傩员O(jiān)聽器中的完整語(yǔ)法定義為:

        public void attributeReplaced(ServletContextAttributeEvent scae) 
        public void attributeReplaced (HttpSessionBindingEvent  hsbe) 
        public void attributeReplaced (ServletRequestAttributeEvent srae)
      
  • 由于這三個(gè)監(jiān)聽器用法極其相似,所以只用一個(gè)例子來(lái)演示具體用法:

  • 新建有一個(gè)JSP頁(yè)面:

      <%@ page language="java" contentType="text/html; charset=UTF-8"
          pageEncoding="UTF-8"%>
      <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
      </head>
      <body>
      <%
          // 向Session數(shù)據(jù)范圍 保存名稱為"name",值為"張三"的屬性(Attribute)
          session.setAttribute("name","張三"); // 觸發(fā)attributeAdd
    
          // 將session中屬性(Attribute)名為"name"的值替換為"李四"
          session.setAttribute("name","李四"); // 觸發(fā)attributeReplaced
    
          // 移除名為"name"的屬性
          session.removeAttribute("name");// 觸發(fā)attributeRemoved
      %>
      </body>
      </html>
    
  • MyHttpSessionAttributeListener:

      package cn.itcast.servlet.listener;
      
      import javax.servlet.http.HttpSession;
      import javax.servlet.http.HttpSessionAttributeListener;
      import javax.servlet.http.HttpSessionBindingEvent;
      
      public class MyHttpSessionAttributeListener implements
              HttpSessionAttributeListener {
      
          @Override
          public void attributeAdded(HttpSessionBindingEvent se) {
              // 屬性添加
              System.out.println("向session添加了一個(gè)屬性...");
              HttpSession session = se.getSession();
      
              System.out.println("屬性名稱:" + se.getName());
              System.out.println("屬性值:" + session.getAttribute(se.getName())); // se.getValue :這個(gè)方法不會(huì)返回當(dāng)前屬性(Attribute)的值
          }
      
          @Override
          public void attributeRemoved(HttpSessionBindingEvent se) {
              // 屬性移除
              System.out.println("從session移除了一個(gè)屬性....");
      
              System.out.println("屬性名稱:" + se.getName());
      
          }
      
          @Override
          public void attributeReplaced(HttpSessionBindingEvent se) {
              // 屬性替換
              System.out.println("將session中一個(gè)屬性值替換為其他值...");
      
              HttpSession session = se.getSession();
              System.out.println("屬性名稱:" + se.getName());
              System.out.println("屬性值:" + session.getAttribute(se.getName()));
          }
      
      }
    

5、監(jiān)聽綁定到 HttpSession 域中的某個(gè)對(duì)象的狀態(tài)的事件監(jiān)聽器

  • 保存在 Session 域中的對(duì)象可以有多種狀態(tài):

    • 綁定到 Session 中;

    • 從 Session 域中解除綁定;

    • 隨 Session 對(duì)象持久化到一個(gè)存儲(chǔ)設(shè)備中(鈍化);

    • 隨 Session 對(duì)象從一個(gè)存儲(chǔ)設(shè)備中恢復(fù)(活化)

  • Servlet 規(guī)范中定義了兩個(gè)特殊的監(jiān)聽器接口來(lái)幫助 JavaBean 對(duì)象了解自己在 Session 域中的這些狀態(tài):HttpSessionBindingListener 接口和 HttpSessionActivationListener 接口,實(shí)現(xiàn)這兩個(gè)接口的類不需要 web.xml 文件中注冊(cè),因?yàn)楸O(jiān)聽方法調(diào)用,都是由Session自主完成的

    • HttpSessionBindingListener

      實(shí)現(xiàn)該接口的 Java 對(duì)象,可以感知自己被綁定到 Session 或者從 Session 中解除綁定

    • HttpSessionActivationListener

      實(shí)現(xiàn)該接口的 Java 對(duì)象,可以感知自己從內(nèi)存被鈍化硬盤上,或者從硬盤被活化到內(nèi)存中

5.1、HttpSessionBindingListener接口

  • 實(shí)現(xiàn)了 HttpSessionBindingListener 接口的 JavaBean 對(duì)象可以感知自己被綁定到 Session 中和從 Session 中刪除的事件

    • 綁定:當(dāng)對(duì)象被綁定到 HttpSession 對(duì)象中時(shí),web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 valueBound 方法

        public void valueBound(HttpSessionBindingEvent event)
      
    • 解綁:當(dāng)對(duì)象從 HttpSession 對(duì)象中解除綁定時(shí),web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 valueUnbound 方法

        public void valueUnbound(HttpSessionBindingEvent event)
      
  • demo:

    • 實(shí)現(xiàn) HttpSessionBindingListener 接口的 JavaBean :

        package cn.itcast.domain;
        
        import javax.servlet.http.HttpSessionBindingEvent;
        import javax.servlet.http.HttpSessionBindingListener;
        
        /**
         * 使Bean1對(duì)象感知 自我被綁定Session中,感知自我被Session解除綁定
         * 
         * @author seawind
         * 
         */
        public class Bean1 implements HttpSessionBindingListener {
            private int id;
            private String name;
        
            public int getId() {
                return id;
            }
        
            public void setId(int id) {
                this.id = id;
            }
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            @Override
            public void valueBound(HttpSessionBindingEvent event) {
                System.out.println("Bean1對(duì)象被綁定了...");
                // 當(dāng)前對(duì)象,操作對(duì)象
                System.out.println("綁定對(duì)象name:" + this.name);
            }
        
            @Override
            public void valueUnbound(HttpSessionBindingEvent event) {
                System.out.println("Bean1對(duì)象被解除綁定了...");
                System.out.println("解除綁定對(duì)象name:" + this.name);
            }
        
        }
      
    • 新建一個(gè)JSP頁(yè)面,里面有如下代碼:

        <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8"%>
        <%@page import="cn.itcast.domain.Bean1"%>
        <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
        <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        </head>
        <body>
        <%
            Bean1 bean_Susan = new Bean1();
            bean_Susan.setId(100);
            bean_Susan.setName("Susan");
            
            // 將bean_Susan對(duì)象以“bean1”為名,綁定到Session中
            session.setAttribute("bean1",bean_Susan);
            
            Bean1 bean_Mary = new Bean1();
            bean_Mary.setId(200);
            bean_Mary.setName("Mary");
      
            // 將bean_Susan對(duì)象以“bean1”為名,綁定到Session中
            session.setAttribute("bean1",bean_Mary);
        %>
        ${bean1.name }
        </body>
        </html>
      
    • 此時(shí)若開啟服務(wù)器,打開這個(gè)JSP頁(yè)面,控制臺(tái)會(huì)輸出什么呢?

        Bean1對(duì)象被綁定了...
        綁定對(duì)象name:bean_Susan
        Bean1對(duì)象被綁定了...
        綁定對(duì)象name:bean_Mary
        Bean1對(duì)象被解除綁定了...
        解除綁定對(duì)象name:bean_Susan
      
    • 注意陷阱:當(dāng) Session 綁定的 JavaBean 對(duì)象替換時(shí),會(huì)讓新對(duì)象綁定,舊對(duì)象解綁

5.2、HttpSessionActivationListener接口

  • 實(shí)現(xiàn)了HttpSessionActivationListener接口的 JavaBean 對(duì)象可以感知自己被活化和鈍化的事件

    • 當(dāng)綁定到 HttpSession 對(duì)象中的 JavaBean 對(duì)象將要隨 HttpSession 對(duì)象被鈍化之前,web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 void sessionWillPassivate(HttpSessionBindingEvent event) 方法

    • 當(dāng)綁定到 HttpSession 對(duì)象中的 JavaBean 對(duì)象將要隨 HttpSession 對(duì)象被活化之后,web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 void sessionDidActive(HttpSessionBindingEvent event) 方法

  • 使用場(chǎng)景:Session保存數(shù)據(jù),很長(zhǎng)一段時(shí)間沒(méi)用,但是不能銷毀Session對(duì)象,又不想占用服務(wù)器內(nèi)存資源 ----- 鈍化(將服務(wù)器內(nèi)存中數(shù)據(jù)序列化硬盤上)

  • 鈍化和活化應(yīng)該由 Tomcat 服務(wù)器 自動(dòng)進(jìn)行,所以應(yīng)該配置 Tomcat :

      <Context>
          <!-- 1分鐘不用 進(jìn)行鈍化  表示1分鐘 -->
          <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
              <!-- 鈍化后文件存儲(chǔ)位置  directory="it315" 存放到it315目錄-->
              <Store className="org.apache.catalina.session.FileStore" directory="it315"/>
          </Manager>
      </Context>
    

    配置context有幾個(gè)位置?

    1、tomcat/conf/context.xml 對(duì)所有虛擬主機(jī) 所有web工程生效

    2、tomcat/conf/Catalina/localhost/context.xml 對(duì)當(dāng)前虛擬主機(jī)所有web工程生效

    3、當(dāng)前工程/META-INF/context.xml 對(duì)當(dāng)前工程有效

  • demo:

    • write.jsp:

        <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8"%>
        <%@page import="cn.itcast.domain.Bean2"%>
        <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
        <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        </head>
        <body>
        <!-- 將javabean 對(duì)象保存Session中 -->
        <%
            Bean2 bean2 = new Bean2();
            bean2.setName("聯(lián)想筆記本");
            bean2.setPrice(5000);
            
            session.setAttribute("bean2",bean2);
        %>
        </body>
        </html>
      
    • read.jsp:

        <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8"%>
        <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
        <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        </head>
        <body>
        <!-- 讀取javabean對(duì)象的數(shù)據(jù) -->
        讀取bean2的數(shù)據(jù): ${bean2.name } , ${bean2.price }
        </body>
        </html>
      
    • Bean2.java:

        package cn.itcast.domain;
        
        import javax.servlet.http.HttpSessionActivationListener;
        import javax.servlet.http.HttpSessionEvent;
        
        /**
         * 感知鈍化和活化
         * 
         * @author seawind
         * 
         */
        public class Bean2 implements HttpSessionActivationListener {
            private String name;
            private double price;
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            public double getPrice() {
                return price;
            }
        
            public void setPrice(double price) {
                this.price = price;
            }
        
            @Override
            public void sessionDidActivate(HttpSessionEvent se) {
                System.out.println("bean2對(duì)象被活化...");
            }
        
            @Override
            public void sessionWillPassivate(HttpSessionEvent se) {
                System.out.println("bean2對(duì)象被鈍化...");
            }
        
        }
      
    • 配置Tomcat后,開啟服務(wù)器,打開write.jsp,等待一分鐘,在這一分鐘內(nèi),打開read.jsp是可以讀取到bean2對(duì)象的。一分鐘過(guò)后,read.jsp讀取不到bean2對(duì)象了,同時(shí)控制臺(tái)輸出:

        bean2對(duì)象被鈍化...
      
    • 這時(shí)候就實(shí)現(xiàn)了鈍化的效果。好,接下來(lái)按照步驟走,鈍化后it315目錄在哪里? 在項(xiàng)目文件夾是找不到這個(gè)目錄的,得去Tomcat服務(wù)器的目錄去找:

        tomcat/work/Catalina/localhost/項(xiàng)目工程名/
      
    • 在it315目錄中的確可以看到一個(gè)XXXXXX.session的文件,其中XXXXXX就是SessionId,打開這個(gè)文件,卻沒(méi)有發(fā)現(xiàn) JavaBean 的任何信息,這是為什么呢?

    • 回顧JavaSE的知識(shí),可以發(fā)現(xiàn) JavaBean 沒(méi)有序列化。Java對(duì)象如果想被序列化,必須實(shí)現(xiàn)Serializable接口 ---- Bean2 實(shí)現(xiàn)該接口:

        import java.io.Serializable;
        ...
        public class Bean2 implements HttpSessionActivationListener, Serializable {
        ...
        }
      
    • 接下來(lái),重啟服務(wù)器,打開write.jsp,一分鐘后,控制臺(tái)照舊輸出bean2對(duì)象被鈍化的消息,再進(jìn)去XXXXXX.session文件中,發(fā)現(xiàn)可以找到 JavaBean 對(duì)象的相關(guān)信息(雖然是亂碼)。接下來(lái)再打開read.jsp,發(fā)現(xiàn)可以讀取到 JavaBean 對(duì)象了,并且此時(shí)控制臺(tái)輸出:

        bean2對(duì)象被活化...
      
    • 這就是一種對(duì)于Session的優(yōu)化策略

5.3、案例:在線用戶列表和踢人功能

  • 圖解:

  • demo:

    • MyServletContextListener:

        package cn.itcast.listener;
        
        import java.util.HashMap;
        import java.util.Map;
        
        import javax.servlet.ServletContext;
        import javax.servlet.ServletContextEvent;
        import javax.servlet.ServletContextListener;
        import javax.servlet.http.HttpSession;
        
        import cn.itcast.domain.User;
        
        /**
         * 完成全局?jǐn)?shù)據(jù)對(duì)象初始化
         * 
         * @author seawind
         * 
         */
        public class MyServletContextListener implements ServletContextListener {
        
            @Override
            public void contextDestroyed(ServletContextEvent sce) {
            }
        
            @Override
            public void contextInitialized(ServletContextEvent sce) {
                // 所有在線用戶數(shù)據(jù)集合
                Map<User, HttpSession> map = new HashMap<User, HttpSession>();
                // 將集合保存ServletContext 數(shù)據(jù)范圍
                ServletContext servletContext = sce.getServletContext();
        
                servletContext.setAttribute("map", map);
            }
        
        }
      
    • JavaBean:

        package cn.itcast.domain;
        
        import java.util.Map;
        
        import javax.servlet.ServletContext;
        import javax.servlet.http.HttpSession;
        import javax.servlet.http.HttpSessionBindingEvent;
        import javax.servlet.http.HttpSessionBindingListener;
        
        /**
         * User對(duì)象自我感知,綁定Session和解除綁定
         */
        public class User implements HttpSessionBindingListener {
            private int id;
            private String username;
            private String password;
            private String role;
        
            public int getId() {
                return id;
            }
        
            public void setId(int id) {
                this.id = id;
            }
        
            public String getUsername() {
                return username;
            }
        
            public void setUsername(String username) {
                this.username = username;
            }
        
            public String getPassword() {
                return password;
            }
        
            public void setPassword(String password) {
                this.password = password;
            }
        
            public String getRole() {
                return role;
            }
        
            public void setRole(String role) {
                this.role = role;
            }
        
            @Override
            public void valueBound(HttpSessionBindingEvent event) {
                // 將新建立Session 和 用戶 保存ServletContext 的Map中
                HttpSession session = event.getSession();
                ServletContext servletContext = session.getServletContext();
        
                Map<User, HttpSession> map = (Map<User, HttpSession>) servletContext
                        .getAttribute("map");
        
                // 將新用戶加入map
                map.put(this, session);
            }
        
            @Override
            public void valueUnbound(HttpSessionBindingEvent event) {
                // 根據(jù)user對(duì)象,從Map中移除Session
                HttpSession session = event.getSession();
                ServletContext servletContext = session.getServletContext();
        
                Map<User, HttpSession> map = (Map<User, HttpSession>) servletContext
                        .getAttribute("map");
        
                // 從map移除
                map.remove(this);
            }
        
        }
      
    • LoginServlet:

        package cn.itcast.servlet;
        
        import java.io.IOException;
        import java.sql.SQLException;
        import java.util.Map;
        
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServlet;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import javax.servlet.http.HttpSession;
        
        import org.apache.commons.dbutils.QueryRunner;
        import org.apache.commons.dbutils.handlers.BeanHandler;
        
        import cn.itcast.domain.User;
        import cn.itcast.utils.JDBCUtils;
        
        /**
         * 登陸
         * 
         * @author seawind
         * 
         */
        public class LoginServlet extends HttpServlet {
        
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                request.setCharacterEncoding("utf-8");
        
                String username = request.getParameter("username");
                String password = request.getParameter("password");
        
                String sql = "select * from user where username = ? and password = ?";
                Object[] args = { username, password };
        
                QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
                try {
                    User user = queryRunner.query(sql,
                            new BeanHandler<User>(User.class), args);
                    // 判斷登陸是否成功
                    if (user == null) {
                        // 失敗
                        request.setAttribute("msg", "用戶名或者密碼錯(cuò)誤!");
                        request.getRequestDispatcher("/login.jsp").forward(request,
                                response);
                    } else {
                        // 成功
                        request.getSession().invalidate();// 銷毀之前狀態(tài)
        
                        // 先判斷該用戶是否已經(jīng)登陸,如果已經(jīng)登陸,將Session銷毀
                        Map<User, HttpSession> map = (Map<User, HttpSession>) getServletContext()
                                .getAttribute("map");
                        for (User hasLoginUser : map.keySet()) {
                            if (hasLoginUser.getUsername().equals(user.getUsername())) {
                                // 此用戶之前登陸過(guò) --- 消滅Session
                                HttpSession hasLoginSession = map.get(hasLoginUser);
                                hasLoginSession.invalidate();// session 被摧毀,移除所有對(duì)象
                                // 若不使用break,則會(huì)調(diào)用map的remove方法(invalidate -> valueUnbound),發(fā)生并發(fā)異常
                                break; 
                            }
                        }
        
                        request.getSession().setAttribute("user", user); // 將user對(duì)象綁定到Session,觸發(fā)valueBound
                        response.sendRedirect("/day18kick/list.jsp");
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        
            public void doPost(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                doGet(request, response);
            }
        
        }
      
    • KickServlet:

        package cn.itcast.servlet;
        
        import java.io.IOException;
        import java.util.Map;
        
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServlet;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import javax.servlet.http.HttpSession;
        
        import cn.itcast.domain.User;
        
        /**
         * 接收被踢id
         * 
         * @author seawind
         * 
         */
        public class KickServlet extends HttpServlet {
        
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                String id = request.getParameter("id");// 被踢人 id
        
                Map<User, HttpSession> map = (Map<User, HttpSession>) getServletContext()
                        .getAttribute("map");
                // 查找目標(biāo)id
                for (User hasLoginUser : map.keySet()) {
                    if (hasLoginUser.getId() == Integer.parseInt(id)) {
                        // 找到被踢用戶記錄
                        HttpSession hasLoginSession = map.get(hasLoginUser);
                        hasLoginSession.invalidate();
                        break;
                    }
                }
                // 跳轉(zhuǎn)回 列表頁(yè)面
                response.sendRedirect("/day18kick/list.jsp");
            }
        
            public void doPost(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                doGet(request, response);
            }
        
        }
      
    • list.jsp:

        <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8"%>
        <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
        <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
        <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        </head>
        <body>
        <h1>在線用戶列表</h1>
        <h2>當(dāng)前用戶 ${user.username }</h2>
        <!-- 將ServletContext中 map 數(shù)據(jù)顯示出來(lái) -->
        <c:forEach items="${map}" var="entry">
            <!-- 只有管理員可以踢人 -->
            <!-- 管理員不能被踢 -->
            ${entry.key.username } 
            <c:if test="${user.role == 'admin' && entry.key.role != 'admin' }">
                <a href="/day18kick/kick?id=${entry.key.id}">踢下線</a> 
            </c:if>
            <br/>
        </c:forEach>
        </body>
        </html>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 監(jiān)聽器(listener) 監(jiān)聽器簡(jiǎn)介 :監(jiān)聽器就是一個(gè)實(shí)現(xiàn)特定接口的普通java程序,這個(gè)程序?qū)iT用于監(jiān)聽另一個(gè)...
    奮斗的老王閱讀 2,671評(píng)論 0 53
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,623評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,988評(píng)論 0 11
  • 本文包括:1、Filter簡(jiǎn)介2、Filter是如何實(shí)現(xiàn)攔截的?3、Filter開發(fā)入門4、Filter的生命周期...
    廖少少閱讀 7,503評(píng)論 3 56
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,753評(píng)論 11 349

友情鏈接更多精彩內(nèi)容