一、基本概念
JavaWeb里面的listener是通過觀察者設(shè)計(jì)模式進(jìn)行實(shí)現(xiàn)的。對于觀察者模式,這里不做過多介紹,大概講一下什么意思。
觀察者模式又叫發(fā)布訂閱模式或者監(jiān)聽器模式。在該模式中有兩個角色:觀察者和被觀察者(通常也叫做主題)。觀察者在主題里面注冊自己感興趣的事件,當(dāng)這個事件發(fā)生時,主題會通過回調(diào)接口的方式通知觀察者。
舉個生活中的例子:訂閱報(bào)紙。任何一個家庭或個人都可以向報(bào)社訂閱報(bào)紙。這里報(bào)社就是“主題”,家庭就是“觀察者”。比如家庭需要訂閱明天早晨的報(bào)紙,這個就是“事件”。到了第二天早晨,報(bào)紙生產(chǎn)出來了,這個就是“事件發(fā)生”。當(dāng)事件發(fā)生時,送報(bào)員將報(bào)紙送到家庭的信箱里面,這里的信箱就是“回調(diào)接口”。
對于JavaWeb里面的監(jiān)聽器,Servlet規(guī)范定義了一些列的Listener接口類,通過接口類的方式將事件暴露給應(yīng)用程序,應(yīng)用程序如果想監(jiān)聽其感興趣的事件,那么不必去直接注冊對應(yīng)的事件,而是編寫自己的listener實(shí)現(xiàn)相應(yīng)的接口類,并將自己的listener注冊到servlet容器。當(dāng)程序關(guān)心的事件發(fā)生時,servlet容器會通知listener,回調(diào)listener里面的方法。這里自定義的listener就是觀察者,servlet容器就是主題。
二、樣例分析
上面說了,servlet容器是通過Listener接口類將事件暴露給應(yīng)用程序的。所以我們與其說是注冊事件,不如說是注冊監(jiān)聽器。對應(yīng)到編程步驟就是:1.編寫自己的listener,實(shí)現(xiàn)特定的Listener接口。2.在web.xml里面注冊自己的listener。這里以最簡單的監(jiān)聽器接口ServletContextListener舉例:
1.TestListener.java
1
2
3
4
5
6
7
8
9
|
public class TestListener implements ServletContextListener { public TestListener() {} public void contextInitialized(ServletContextEvent sce) { System.out.println( "ServletContextListener.contextInitialized" ); } public void contextDestroyed(ServletContextEvent sce) { System.out.println( "ServletContextListener.contextDestroyed" ); } } |
2.web.xml
1
2
3
|
<listener> <listener- class >com.nantang.listener.TestListener</listener- class > </listener> |
當(dāng)容器啟動時會向日志中輸出"ServletContextListener.contextInitialized",當(dāng)容器關(guān)閉時會輸出"ServletContextListener.contextDestroyed"。詳細(xì)的解釋后面會進(jìn)一步分析。
這里需要注意是,如果在IDE(Eclipse、STS等)演示上面的例子,當(dāng)啟動服務(wù)器時,在控制臺可以看到"ServletContextListener.contextInitialized",當(dāng)關(guān)閉服務(wù)器時,是看不到"ServletContextListener.contextDestroyed"的。這不是沒有執(zhí)行contextDestroyed方法,而是IDE實(shí)現(xiàn)的不夠完美。要想驗(yàn)證確實(shí)調(diào)用了contextDestroyed,可以在contextDestroyed里面寫一段代碼邏輯往文件輸出內(nèi)容而不要輸出到控制臺。
三、源碼分析
現(xiàn)在我們分析下,servlet規(guī)范為我們定義了哪些事件。更準(zhǔn)確的說是定義了哪些監(jiān)聽接口。下面的介紹都是以servlet3.0規(guī)范為準(zhǔn)。
servlet3.0為我們提供了8個監(jiān)聽器接口,按照它們的作用域來劃分的話可以分為三類:
1.servlet上下文相關(guān)監(jiān)聽接口,包括:ServletContextListener和ServletContextAttributeListener。
2.http session相關(guān)監(jiān)聽接口,包括:HttpSessionListener、HttpSessionActivationListener、HttpSessionAttributeListener和HttpSessionBindingListener。
3.servlet request相關(guān)監(jiān)聽接口,包括:ServletRequestListener和ServletRequestAttributeListener。
其實(shí)從接口的命名,各位看官應(yīng)該能猜出其基本功能。下面我們按分類來解釋。
1.servlet上下文相關(guān)監(jiān)聽接口
之前在介紹Servlet的時候,我們解釋過一個web應(yīng)用對應(yīng)一個servlet上下文。所以ServletContextListener和ServletContextAttributeListener監(jiān)聽的事件的生命范圍是貫穿整個web應(yīng)用的。下面是這兩個接口的類圖層級關(guān)系。
1.1 EventListener
EventListener是一個標(biāo)記接口,所有的事件監(jiān)聽器都必須繼承這個接口,這就是servlet規(guī)范,沒什么好解釋的。
1.2 EventObject
和EventListener類似,EventObject是個事件頂級類,所有具體的事件類都必須繼承EventObject。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class EventObject implements java.io.Serializable { protected transient Object source; public EventObject(Object source) { if (source == null ) throw new IllegalArgumentException( "null source" ); this .source = source; } public Object getSource() { return source; } public String toString() { return getClass().getName() + "[source=" + source + "]" ; } } |
這個類很簡單,其本質(zhì)就一個東西:source。通過類名EventObject和屬性名source,就能看出這個類就干了一件事,持有“事件源對象”。
1.3 ServletContextEvent
1
2
3
4
5
6
7
8
|
public class ServletContextEvent extends java.util.EventObject { public ServletContextEvent(ServletContext source) { super (source); } public ServletContext getServletContext () { return (ServletContext) super .getSource(); } } |
servlet上下文事件,這個事件類就是對EventObject的簡單繼承。構(gòu)造方法中提供ServletContext實(shí)例作為事件源。因?yàn)槭录词莝ervlet上下文,所以提供個getServletContext獲取ServletContext實(shí)例。
在我們后續(xù)講解其他事件類的時候,都是一個模子,每個事件類都提供相應(yīng)的構(gòu)造方法,傳入相應(yīng)的事件源對象,并提供額外的獲取事件源方法。所以EventObject就是個事件源的基類,所有事件子類的本質(zhì)就干了一件事,確定具體的事件源對象。
所以我們后面講解事件的地方,一帶而過。
1.4 ServletContextListener
1
2
3
4
|
public interface ServletContextListener extends EventListener { public void contextInitialized ( ServletContextEvent sce ); public void contextDestroyed ( ServletContextEvent sce ); } |
servlet上下文監(jiān)聽器接口,對應(yīng)著兩個事件:servlet上下文初始化事件和servlet上下文即將關(guān)閉事件。
當(dāng)web應(yīng)用初始化的時候,servlet容器會構(gòu)造ServletContextEven實(shí)例,并回調(diào)contextInitialize方法。
當(dāng)servlet上下文即將關(guān)閉時,一般是關(guān)閉服務(wù)器之前,servlet容器會構(gòu)造ServletContextEven實(shí)例,并回調(diào)contextDestroyed方法。這里需要注意的是,contextDestroyed方法的執(zhí)行會在所有的servlet和filter執(zhí)行完destroy方法之后。
所以如果我們想在應(yīng)用啟動或關(guān)閉時需要做些事情的話,就編寫自己的listener實(shí)現(xiàn)該接口。
所有的事件監(jiān)聽器也是一個模子,按照servlet規(guī)范定義相應(yīng)的事件回調(diào)接口方法,方法的入?yún)⒕褪窍鄳?yīng)的事件源實(shí)例。所以我們后面講解監(jiān)聽器的地方也一帶而過。
1.5 ServletContextAttributeEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class ServletContextAttributeEvent extends ServletContextEvent { private String name; private Object value; public ServletContextAttributeEvent(ServletContext source, String name, Object value) { super (source); this .name = name; this .value = value; } public String getName() { return this .name; } public Object getValue() { return this .value; } } |
ServletContextAttributeEvent表示servlet上下文屬性相關(guān)事件,一般當(dāng)屬性發(fā)生改變時會觸發(fā)該事件。這個類繼承ServletContextEven,事件源也是ServletContext實(shí)例。額外提供屬性名和屬性值的獲取方法。
1.6 ServletContextAttributeListener
1
2
3
4
5
|
public interface ServletContextAttributeListener extends EventListener { public void attributeAdded(ServletContextAttributeEvent scab); public void attributeRemoved(ServletContextAttributeEvent scab); public void attributeReplaced(ServletContextAttributeEvent scab); } |
當(dāng)servlet上文屬性發(fā)生增、刪、改的時候,servlet容器構(gòu)造ServletContextAttributeEvent事件對象,分別回調(diào)attributeAdded、attributeRemoved、attributeReplaced方法。
這里需要注意的是attributeReplaced方法,當(dāng)屬性的值被替換的時候回調(diào)。這個時候如果調(diào)用ServletContextAttributeEvent.getValue()方法返回的是替換之前的屬性值。
2 http session相關(guān)監(jiān)聽接口
2.1 HttpSessionEvent
1
2
3
4
5
6
7
8
|
public class HttpSessionEvent extends java.util.EventObject { public HttpSessionEvent(HttpSession source) { super (source); } public HttpSession getSession () { return (HttpSession) super .getSource(); } } |
http session相關(guān)事件,當(dāng)session發(fā)生變化時會觸發(fā)該事件。事件源是HttpSession實(shí)例,并提供額外的HttpSession獲取方法。
2.2 HttpSessionListener
1
2
3
4
|
public interface HttpSessionListener extends EventListener { public void sessionCreated ( HttpSessionEvent se ); public void sessionDestroyed ( HttpSessionEvent se ); } |
當(dāng)session被創(chuàng)建和銷毀的時候,servlet容器構(gòu)造HttpSessionEvent事件對象,并回調(diào)sessionCreated和sessionDestroyed方法。
2.3 HttpSessionActivationListener
1
2
3
4
|
public interface HttpSessionActivationListener extends EventListener { public void sessionWillPassivate(HttpSessionEvent se); public void sessionDidActivate(HttpSessionEvent se); } |
當(dāng)session將要鈍化或已被激活時,servlet容器構(gòu)造HttpSessionEvent事件對象,回調(diào)sessionWillPassivate和sessionDidActivate方法。
這里解釋下鈍化和激活:鈍化是指服務(wù)器內(nèi)存不夠了或者session的活動超時時間到了,把最近不活動的session序列化到磁盤。激活是指某個鈍化的session又被訪問了,從磁盤將session反序列化到內(nèi)存。
這里可以看出要想鈍化和激活,首先session得可序列化和反序列化。同時我們在編程過程中,session盡量用String、Integer等簡單的對象,盡量不要用list、map等集合。
2.4 HttpSessionBindingEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class HttpSessionBindingEvent extends HttpSessionEvent { private String name; private Object value; public HttpSessionBindingEvent(HttpSession session, String name) { super (session); this .name = name; } public HttpSessionBindingEvent(HttpSession session, String name, Object value) { super (session); this .name = name; this .value = value; } public HttpSession getSession () { return super .getSession(); } public String getName() { return name; } public Object getValue() { return this .value; } } |
http session的屬性相關(guān)事件,當(dāng)session屬性發(fā)生變化時會觸發(fā)該事件。事件源是HttpSession實(shí)例,并提供額外的獲取HttpSession、屬性名、屬性值的方法。
2.5 HttpSessionAttributeListener
1
2
3
4
5
|
public interface HttpSessionAttributeListener extends EventListener { public void attributeAdded ( HttpSessionBindingEvent se ); public void attributeRemoved ( HttpSessionBindingEvent se ); public void attributeReplaced ( HttpSessionBindingEvent se ); } |
當(dāng)session屬性發(fā)生增、刪、改的時候,servlet容器構(gòu)造HttpSessionBindingEvent事件對象,分別回調(diào)attributeAdded、attributeRemoved、attributeReplaced方法。
這里需要注意的是attributeReplaced方法,當(dāng)屬性的值被替換的時候回調(diào)。這個時候如果調(diào)用ServletContextAttributeEvent.getValue()方法返回的是替換之前的屬性值。
當(dāng)調(diào)用session的invalidate方法或者session失效時,也會回調(diào)attributeRemoved方法。
2.6 HttpSessionBindingListener
1
2
3
4
|
public interface HttpSessionBindingListener extends EventListener { public void valueBound(HttpSessionBindingEvent event); public void valueUnbound(HttpSessionBindingEvent event); } |
這個監(jiān)聽器也是監(jiān)聽session的屬性變化。當(dāng)session屬性發(fā)生增和刪,也就是屬性值綁定和屬性值解綁的時候,servlet容器構(gòu)造HttpSessionBindingEvent事件對象,分別回調(diào)valueBound、valueUnbound方法。
這么一看和HttpSessionAttributeListener沒什么區(qū)別,其實(shí)不是這樣。兩者有個本質(zhì)的區(qū)別就是事件觸發(fā)的條件。
當(dāng)session的屬性有任何的變化,servlet容器都會通知HttpSessionAttributeListener。但是對于HttpSessionBindingListener,只有當(dāng)綁定或解綁的屬性值是監(jiān)聽器的實(shí)例時,servlet容器才會通知。舉例來說:
1
2
3
4
5
6
7
8
9
10
|
public class TestListener implements HttpSessionBindingListener{ @Override public void valueBound(HttpSessionBindingEvent event) { System.out.println( "HttpSessionBindingListener.valueBound" ); } @Override public void valueUnbound(HttpSessionBindingEvent event) { System.out.println( "HttpSessionBindingListener.valueUnbound" ); } } |
我們自定義監(jiān)聽器TestListener實(shí)現(xiàn)HttpSessionBindingListener,下面我們在代碼中設(shè)置如下session屬性:
1
2
3
4
|
HttpSession session = request.getSession(); TestListener testListener= new TestListener(); session.setAttribute( "listener" , testListener); session.removeAttribute( "listener" ); |
這里session的屬性值是我們的監(jiān)聽器TestListener實(shí)例。所以這段代碼執(zhí)行時,servlet容器會通知TestListener并回調(diào)valueBound和valueUnbound方法。
這里需要注意的是,當(dāng)調(diào)用session的invalidate方法或者session失效時,也會回調(diào)valueUnbound方法。
3 servlet request相關(guān)監(jiān)聽接口
3.1 ServletRequestEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class ServletRequestEvent extends java.util.EventObject { private ServletRequest request; public ServletRequestEvent(ServletContext sc, ServletRequest request) { super (sc); this .request = request; } public ServletRequest getServletRequest () { return this .request; } public ServletContext getServletContext () { return (ServletContext) super .getSource(); } } |
servlet請求的相關(guān)事件,當(dāng)request發(fā)生變化時會觸發(fā)該事件。事件源是ServletContext實(shí)例,并提供額外的獲取ServletContext和ServletRequest方法。
3.2 ServletRequestListener
1
2
3
4
|
public interface ServletRequestListener extends EventListener { public void requestDestroyed ( ServletRequestEvent sre ); public void requestInitialized ( ServletRequestEvent sre ); } |
當(dāng)請求初始化或者銷毀時,即客戶端請求進(jìn)入web應(yīng)用(進(jìn)入servlet或者第一個filter)或web應(yīng)用返回響應(yīng)給客戶端(退出servlet或者第一個filter)。servlet容器構(gòu)造ServletRequestEvent實(shí)例,回調(diào)requestInitialized和requestDestroyed方法。
3.3 ServletRequestAttributeEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class ServletRequestAttributeEvent extends ServletRequestEvent { private String name; private Object value; public ServletRequestAttributeEvent(ServletContext sc, ServletRequest request, String name, Object value) { super (sc, request); this .name = name; this .value = value; } public String getName() { return this .name; } public Object getValue() { return this .value; } } |
servlet請求屬性的相關(guān)事件,當(dāng)請求屬性發(fā)生變化時會觸發(fā)該事件。事件源是ServletContext實(shí)例,并提供額外的獲取屬性名和屬性值的方法。
3.4 ServletRequestAttributeListener
1
2
3
4
5
|
public interface ServletRequestAttributeListener extends EventListener { public void attributeAdded(ServletRequestAttributeEvent srae); public void attributeRemoved(ServletRequestAttributeEvent srae); public void attributeReplaced(ServletRequestAttributeEvent srae); } |
當(dāng)請求的屬性發(fā)生增、刪、改的時候,servlet容器構(gòu)造ServletRequestAttributeEvent事件對象,分別回調(diào)attributeAdded、attributeRemoved、attributeReplaced方法。
這里需要注意的是attributeReplaced方法,當(dāng)屬性的值被替換的時候回調(diào)。這個時候如果調(diào)用ServletRequestAttributeEvent.getValue()方法返回的是替換之前的屬性值。
四、總結(jié)
至此,listener講完了。我們可以發(fā)現(xiàn)listener和servlet、filter有個共同點(diǎn),都是由容器進(jìn)行調(diào)度。我們只需要編寫自己的listener去實(shí)現(xiàn)我們關(guān)心的監(jiān)聽器接口并注冊,剩下來的工作就是在我們自己的listener里面編寫業(yè)務(wù)邏輯。
這邊博文介紹的listener是servlet3.0規(guī)范制定的。3.1已經(jīng)增加了一些事件監(jiān)聽器接口,道理都是類似的,讀者可以自行去了解。
原文鏈接:http://www.cnblogs.com/nantang/p/5899546.html