29、servlet事件監聽器(JavaEE筆記)

一、概述

監聽器就是一個實現特定接口的普通java程序,這個程序專門用于監聽另一個java對象的方法調用或屬性改變,當被監聽對象發生上述事件后,監聽器某個方法將立即被執行。

二、監聽器經典案例:監聽windows窗口的事件監聽器

(工程day20
請描述java時間監聽機制:

  • 1.事件監聽涉及到三個組件:事件源、事件對象、事件監聽器
  • 2.當事件源上發生某個動作時,它會調用事件監聽器的一個方法,并在調用該方法時把事件對象傳遞進去,開發人員在監聽器中通過事件對象,就可以拿到事件源,從而對事件源進行操作。事件對象封裝事件源和動作,而監聽器對象通過事件對象對事件源進行處理。

Demo1.java

package cn.itcast.demo;
import java.awt.Frame;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

public class Demo1 {

    public static void main(String[] args) {
        Frame f = new Frame();
        f.setSize(400, 400);
        f.setVisible(true);
        
        f.addWindowListener(new WindowListener() {
            
            public void windowOpened(WindowEvent arg0) {}
            public void windowIconified(WindowEvent arg0) {}
            public void windowDeiconified(WindowEvent arg0) {}
            public void windowDeactivated(WindowEvent arg0) {}

            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("關閉");
                Frame f = (Frame) e.getSource();//得到關閉窗口的事件源
                f.dispose();//關閉窗口  
            }
            public void windowClosed(WindowEvent arg0) {}
            public void windowActivated(WindowEvent arg0) {}
        });
    }
}

說明:這里我們產生一個窗口,當我們點擊窗口右上角的叉時,使用監聽器監測此事件,點擊的時候就會監測到,執行關閉操作,這是一個經典的監聽器使用例子。上例中使用方法addWindowListener注冊一個監聽器,在監聽器中使用相關方法對事件源進行處理,當然我們會將事件源WindowEvent傳遞進去。

三、自己設計一個類讓別人監聽

Demo2.java

package cn.itcast.demo;
//設計一個事件源,被監聽器監聽,Observer(觀察者設計模式)
public class Demo2 {

    public static void main(String[] args) {
        Person p = new Person();
        p.registerListener(new PersonListener() {
            
            @Override
            public void dorun(Event e) {
                Person person = e.getSource();
                System.out.println(person + "吃飯");
            }
            
            @Override
            public void doeat(Event e) {
                Person person = e.getSource();
                System.out.println(person + "跑步");
            }
        });
        p.eat();
    }
}

class Person{//讓這個類被其他類監聽
    
    private PersonListener listener;//定義一個監聽器接口,記住傳遞進來的監聽器對象
    
    public void eat(){
        if(listener != null){
            listener.doeat(new Event(this));
        }
        
    }
    public void run(){
        if(listener != null){
            listener.dorun(new Event(this));
        }
    }
    public void registerListener(PersonListener listener){
        this.listener = listener;
    }
}

interface PersonListener{
    public void doeat(Event e);
    public void dorun(Event e);
}
class Event{//用于封裝事件源
    private Person source;
    
    public Event() {
        super();
    }

    public Event(Person source) {
        super();
        this.source = source;
    }

    public Person getSource() {
        return source;
    }

    public void setSource(Person source) {
        this.source = source;
    }   
}

說明:首先我們定義事件源對象Event和一個監聽器接口PersonListener,然后我們想讓某個類(這里是Person)被監聽,于是需要在類中維護一個監聽器接口PersonListener,我們可以使用一個方法(registerListener)將此接口傳遞進來,然后我們就可以使用監聽器接口中的相關方法對事件源進行處理了。

四、servlet監聽器

  • 在servlet規范中定義了多種類型的監聽器,它們用于監聽的事件源分別為ServletContext、HttpSession 和 ServletRequest這三個域對象。

  • Servlet規范針對這三個對象上的操作,又把這多種類型的監聽器劃分為三種類型:

    • 1.監聽三個域對象創建和銷毀的事件監聽器;
    • 2.監聽域對象中屬性的增加和刪除的事件監聽器;
    • 3.監聽綁定到HttpSession域中的某個對象的狀態的事件監聽器。
  • 監聽servletContext域對象創建和銷毀

    • ServletContextListener接口用于監聽ServletContext對象的創建和銷毀事件。
    • ServletContext對象被創建時,激發````contextInitialized(ServletContextEvent sce)```方法。
    • ServletContext對象被銷毀時,激發contextDestroyed(ServletContextEvent sce)方法。

注意:ServletContext域對象何時創建和銷毀?

  • 創建:服務器啟動針對每一個web應用創建一個ServletContext
  • 銷毀:服務器關閉前先關閉代表每一個web應用的ServletContext

4.1 示例:監聽ServletContext對象

MyServletContextListener.java

package cn.itcast.web.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//這里我們只需要在web.xml文件中將此監聽器配置就可以了,當服務器啟動時就會創建ServletContext
public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext創建");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext銷毀");
    }
}

web.xml中進行注冊:

<listener>
    <listener-class>cn.itcast.web.listener.MyServletContextListener</listener-class>
</listener>

說明:因為在服務器啟動的時候ServletContext就會創建,這時我們可以監測到其創建。

4.2 監聽HttpSession域對象創建和銷毀

這里HttpSessionListener接口用于監聽HttpSession的創建和銷毀。

MyHttpSessionListener.java

package cn.itcast.web.listener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class MyHttpSessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        
        System.out.println(se.getSession() + "session創建了");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session銷毀了");
    }
}

說明:在訪問index.jsp的時候會創建一個session,服務器關閉的時候是不會摧毀session的,我們可以設置失效時間,在配置文件中進行配置,單位是分鐘,可以用來統計當前在線多少用戶,但是不是特別準確。

Session域對象創建和銷毀的時機
創建:用戶每一次訪問時,服務器創建Session
銷毀:如果用戶的Session三十分鐘(默認)沒有使用,服務器就會銷毀Session,我們在web.xml里面也可以配置Session失效時間。

4.3 監聽HttpRequest域對象創建和銷毀

這里ServletRequestListener接口用于監聽ServletRequest對象的創建和銷毀。
MyServletRequestListener .java

package cn.itcast.web.listener;
//ServletRequestListener可以用來檢測網站性能
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

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() + "創建了");

    }
}

說明:ServletRequest域對象創建和銷毀的時機
創建:用戶每一次訪問,都會創建一個Request。
銷毀:當前訪問結束,Request對象就會銷毀。

五、案例:統計當前在線人數

OnlineCountListener.java

package cn.itcast.web.listener;
//統計當前在線用戶個數
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//監聽器和過濾器一樣,Servlet中只存在一個,所以num不需要設置成靜態的
public class OnlineCountListener implements HttpSessionListener {
    //如果我們要將num值傳遞到頁面,則不能使用Request和session,而只能通過Application(ServletContext)
    /*int num = 0;*/

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        ServletContext context = se.getSession().getServletContext();
        Integer num = (Integer) context.getAttribute("num");
        if(num == null){
            context.setAttribute("num", 1);
        }else{
            num++;
            context.setAttribute("num", num);
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        ServletContext context = se.getSession().getServletContext();
        Integer num = (Integer) context.getAttribute("num");
        if(num == null){
            context.setAttribute("num", 1);
        }else{
            num--;
            context.setAttribute("num", num);
        }
    }
}

說明:當服務器啟動時只有這個監聽器只有一個,所以我們可以在方法中定義一個變量來統計在線人數。而這個變量我們如果要傳遞到前臺,不能使用request和session,因為會有多個。這里我們通過servletContext域來將此統計值傳遞到前臺。

index.jsp

<body>
    當前在線用戶個數:${applicationScope.num} 
</body>

六、案例:自定義Session掃描器

在開發中我們有時候需要管理session,比如當session多長時間沒用之后我們就將其銷毀,減小服務器的壓力。
SessionScannerListener.java

package cn.itcast.web.listener;
//Session的默認失效時間是三十分鐘
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

//自定義Session掃描器
public class SessionScannerListener implements HttpSessionListener, ServletContextListener{
    private List<HttpSession> list = Collections.synchronizedList(new LinkedList());//使得集合成為一個線程安全的集合
    private Object lock;//定義一把鎖
    
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        synchronized (lock) {
            list.add(session);
        }
        //list.add(session);//這樣做容易出現兩個Session搶一個list位置的情況,即集合不是線程安全的
        System.out.println("被創建了");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("被銷毀了");

    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        Timer timer = new Timer() ;
        timer.schedule(new MyTask(list,lock), 0, 1000*15);//延時為0,每隔15秒掃描一次  
        
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}
class MyTask extends TimerTask{
    
    private List<HttpSession> list ;
    private Object lock;//定義一把鎖用于記住傳遞進來的鎖
    
    public MyTask(List list, Object lock) {//將要掃描的集合傳遞進來
        this.list = list;
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            ListIterator<HttpSession> it = list.listIterator();
            while(it.hasNext()){
                HttpSession session = (HttpSession) it.next();
                if((System.currentTimeMillis() - session.getLastAccessedTime()) > 1000*60){//表示此Session十五秒沒人用,就將其摧毀
                    session.invalidate();//摧毀此Session
                    //list.remove(session);//將其從當前的list中移除
                    it.remove();//調用迭代器將其移除
                }
            }
        }
        /*ListIterator<HttpSession> it = list.listIterator();
        while(it.hasNext()){
            HttpSession session = (HttpSession) it.next();
            if((System.currentTimeMillis() - session.getLastAccessedTime()) > 1000*60){//表示此Session十五秒沒人用,就將其摧毀
                session.invalidate();//摧毀此Session
                //list.remove(session);//將其從當前的list中移除
                it.remove();//調用迭代器將其移除
            }
        }*/
    }
}

說明:

  • 1.我們定義一個集合來保存所有session,但是當兩個用戶同時訪問的時候,有可能在創建session的時存入集合的同一個位置,為了避免這種情況,我們將集合做成一個線程安全的,java中為我們提供了一個集合幫助類Collections類,可以將集合做成一個線程安全的集合。

  • 2.我們要掃描在線用戶,所以需要定義一個定時器,而此定時器是在服務器一啟動就需要開啟,于是我們還需要一個servletContext的監聽器,我們直接讓定義的監聽器繼承兩個監聽器接口,同時監聽HttsSessionservletContext

  • 3.我們在遍歷集合的時候是不能執行add操作的,這會出現并發問題,所以我們需要給迭代器和add方法都加上一把鎖,防止并發問題。將一段代碼做成同步是只需要加關鍵字synchronized即可,但是如果要把兩段代碼做成同步的就需要用到鎖。

我們還可以指定服務器在某個時間發送郵件:
SendMailListener.java

package cn.itcast.web.listener;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//我們設置一個時間,讓監聽器在設置的時間點干什么事情
public class SendMailListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        
        Calendar c = Calendar.getInstance();
        c.set(2015, 11, 7, 15, 11, 0);//設置一個時間是2015.12.7 15:11:00
        
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            
            @Override
            public void run() {
                System.out.println("aaaaaaaa");
            }
        }, c.getTime());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}

七、監聽三個域對象屬性的變化

  • Servlet規范定義了監聽ServletContext、HttpSession
    HttpServletRequest這三個對象中的屬性變更信息事件的監聽器。

  • 這三個監聽器接口分別是ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener

  • 這三個接口中都定義了三個方法來處理被監聽對象中的屬性的增加,刪除和替換的事件,同一個事件在這三個接口中對應的方法名稱完全相同,只是接受的參數類型不同。

八、相關方法

8.1attributeAdded方法

  • 當向被監聽對象中增加一個屬性時,web容器就調用事件監聽器的attributeAdded方法進行添加操作,這個方法接受一個事件類型的參數,監聽器可以通過這個參數來獲得正在增加屬性的域對象和被保護到域中的屬性對象。

  • 各個域屬性監聽器中的完整語法定義

public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeAdded(HttpSessionBindingEvent se)
public void attributeAdded(ServletRequestAttributeEvent srae)

8.2 attributeRemoved方法

  • 當刪除被監聽對象中的一個屬性時,web容器調用事件監聽器的這個方法進行相應的操作。
  • 各個域屬性監聽器中的完整語法定義
public void attributeRemoved(ServletContextAttributeEvent scab)
public void attributeRemoved(HttpSessionBindingEvent se)
public void attributeRemoved(ServletRequestAttributeEvent srae)

8.3 attributeReplace方法

  • 當監聽器的域對象中的某一個屬性被替換時,web容器調用事件監聽器的這個方法進行相應的操作。
  • 各個域屬性監聽器中的完整語法定義
public void attributeReplaced(ServletContextAttributeEvent scab)
public void attributeReplaced(HttpSessionBindingEvent se)
public void attributeReplaced(ServletRequestAttributeEvent srae)

8.4 感知Session綁定的事件監聽器

  • 保存在Session域中的對象可以有多種狀態。綁定到Session中:從Session域中解決綁定;隨Session對象持久化到一個存儲設備中;隨Session對象從一個存儲設備中恢復。

  • servlet規范中定義兩個特殊的監聽器接口來幫助javaBean對象了解自己在Session域中的這些狀態:HttpSessionBindingListener接口和HttpSessionActivationListener接口,實現這兩個接口的類不需要在web.xml文件中進行注冊。

  • HttpSessionBindingListener接口
    實現了此接口的javaBean對象可以感知自己被綁定到Session中和從Session中刪除的事件。
    例:MyBean .java

package cn.itcast.domain;
//這個監聽器用來監聽自己,所以不需要在配置文件中進行配置
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class MyBean implements HttpSessionBindingListener {
    
    private String name;
    
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        System.out.println("自己被添加到Session");
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        System.out.println("自己被從Session刪除");
    }
}

index.jsp

<% session.setAttribute("bean", new MyBean()); %>
  • HttpSessionActivationListener接口
    實現了此接口的javaBean對象可以感知自己被活化和鈍化的事件。
    MyBean2.java
package cn.itcast.domain;
import java.io.Serializable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
//注意:Session被鈍化和活化都是由tomcat管理,默認是三十分鐘,但是我們也可以自己進行設置。更改服務器的配置
public class MyBean2 implements HttpSessionActivationListener,Serializable {

    @Override
    public void sessionWillPassivate(HttpSessionEvent se) {
        System.out.println("鈍化");//即從內存中序列化到硬盤
    }

    @Override
    public void sessionDidActivate(HttpSessionEvent se) {
        System.out.println("活化");//從硬盤中回到內存
    }
}

同時我們需要一個配置文件context.xml,放在META-INF中。

<Context>
    <manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">1表示一分鐘
        <Store className="org.apache.catalina.session.FileStore" directory="it315"/> it315這個目錄在tomcat的work目錄中找到
    </manager>
</Context>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 監聽器(listener) 監聽器簡介 :監聽器就是一個實現特定接口的普通java程序,這個程序專門用于監聽另一個...
    奮斗的老王閱讀 2,565評論 0 53
  • 本文包括:1、Listener簡介2、Servlet監聽器3、監聽三個域對象創建和銷毀的事件監聽器4、監聽三個域對...
    廖少少閱讀 6,132評論 6 28
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,374評論 11 349
  • 雨夜 嘩啦啦的作響 心情在這一刻泛起漣漪 望向雨夜 仿佛看到了你的面容 伸手想去撫摸 卻又遙不可及 想問你聲,你過...
    Mr丶鹿閱讀 186評論 0 0
  • 因為口語內測組的調試,好一陣忙活,昨天的作業都拉下了。 群主的原圖省略 直接抄寫加注釋 運行地址 http...
    蝸牛0718閱讀 295評論 1 1