標籤:aos 模式 屬性 extends 記憶體 遷移 集合 疑問 sep
一、背景
每次啟動tomcat 時,console 控制台會輸出各種資訊,其中看到這兩條資訊
[xxx 2018-06-04 15:52:13,772](DEBUG) - xxx.xxx.basic.listener.StartUpListener - (StartUpListener.java:53)XXX啟動初始化開始
[xxx 2018-06-04 15:52:13,783](DEBUG) - xxx.xxx.basic.listener.StartUpListener - (StartUpListener.java:57)XXX啟動初始化結束
為什麼在每次啟動tomcat 時會列印這樣的兩條資訊呢,為了一探究竟,我們進入到這個類裡面
@WebListener("startUpListener")public class StartUpListener implements ServletContextListener {/* * log4j日誌記錄 */protected final Logger LOG = Logger.getLogger(this.getClass());/** * * 監聽項目啟動,進行初始化 * * @param sce * ServletContextEvent對象 */@Overridepublic void contextInitialized(ServletContextEvent sce) {// TODO Auto-generated method stubLOG.debug("XXX啟動初始化開始");Const.CONTEXT = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());Const.PROJECT_PATH = sce.getServletContext().getRealPath(Const.SEPARATOR);Const.BASE = sce.getServletContext().getContextPath();LOG.debug("XXX啟動初始化結束");}/** * 監聽項目終止,進行銷毀 * * @param sce * ServletContextEvent對象 */@Overridepublic void contextDestroyed(ServletContextEvent sce) {// TODO Auto-generated method stub}}
在這個類中看到如下字樣“監聽項目啟動,進行初始化”和“監聽項目終止,進行銷毀”,同時在contextInitialized 方法中找到了之前的日誌資訊。再看到這個類繼承了ServletContextListener, 由此我們不難得出結論,該方法的作用是在tomcat 啟動時對項目的啟動過程進行監聽記錄。那麼這個方法為什麼能夠實現監聽呢?
二、Listener 簡介
Listener 即監聽器,是servlet 的監聽器。隨web應用的啟動而啟動,只初始化一次,隨web應用的停止而銷毀(通過觀察者設計模式進行實現的)。主要作用是:做一些初始化的內容添加工作、設定一些基本的內容、比如一些參數或者是一些固定的對象,通過監聽器,可以自動激發一些操作。比如:監聽線上使用者數量等等。
在 Servlet API 中有一個 ServletContextListener 介面,它能夠監聽 ServletContext 對象的生命週期,實際上就是監聽 Web 應用的生命週期。當Servlet 容器啟動或終止Web 應用時,會觸發ServletContextEvent 事件,該事件由ServletContextListener 來處理。在 ServletContextListener 介面中定義了處理ServletContextEvent 事件的兩個方法:
public interface ServletContextListener extends EventListener { //當Servlet 容器啟動Web 應用程式時調用該方法。在調用完該方法之後,容器再對Filter 初始化, 並且對那些在Web 應用程式啟動時就需要被初始化的Servlet 進行初始化。 public void contextInitialized(ServletContextEvent sce); //當Servlet 容器終止Web 應用程式時調用該方法。在調用該方法之前,容器會先銷毀所有的Servlet 和Filter 過濾器。 public void contextDestroyed(ServletContextEvent sce);}
三、案例分析
再回到最開始的疑問,StartUpListener 類實現了ServletContextListener 介面,通過配置@WebListener("startUpListener") 監聽註解的方式,在項目啟動時,調用contextInitialized 方法列印了項目啟動的日誌以及初始化一些參數。另外如果不使用註解的方式實現監聽,也可以再web.xml 中配置該監聽類
<!-- spring 監聽項目啟動過程,初始化參數 --><listener> <listener-class> xxx.xxx.basic.listener.StartUpListener </listener-class> </listener>
四、Servlet Listener介面和事件(Event)對象
按監聽的對象劃分:servlet2.4規範定義的事件有三種:
1.用於監聽應用程式環境對象(ServletContext)的事件監聽器
2.用於監聽使用者會話對象(HttpSession)的事件監聽器
3.用於監聽請求訊息對象(ServletRequest)的事件監聽器
Servlet API提供了以下監聽器介面:javax.servlet.AsyncListener - 如果在添加了接聽程式的ServletRequest上啟動的非同步作業已完成,逾時或導致錯誤,將會通知接聽程式。javax.servlet.ServletContextListener - 用於接收關於ServletContext生命週期更改的通知事件的介面。javax.servlet.ServletContextAttributeListener - 接收關於ServletContext屬性更改的通知事件的介面。javax.servlet.ServletRequestListener - 用於接收關於進入和超出Web應用程式範圍的請求的通知事件的介面。javax.servlet.ServletRequestAttributeListener - 接收關於ServletRequest屬性更改的通知事件的介面。javax.servlet.http.HttpSessionListener - 接收關於HttpSession生命週期更改的通知事件的介面。javax.servlet.http.HttpSessionBindingListener - 使對象從會話綁定到綁定或從其綁定時被通知。javax.servlet.http.HttpSessionAttributeListener - 用於接收關於HttpSession屬性更改的通知事件的介面。javax.servlet.http.HttpSessionActivationListener - 綁定到會話的對象可能會偵聽容器事件,通知他們會話將被鈍化,該會話將被啟用。需要在VM或持久化會話之間遷移會話的容器來通知綁定到實現HttpSessionActivationListener的會話的所有屬性。
按監聽的事件類別項劃分
1.用於監聽域對象自身的建立和銷毀的事件監聽器
2.用於監聽域對象中的屬性的增加和刪除的事件監聽器
3.用於監聽綁定到HttpSession域中的某個對象的狀態的事件監聽器
Servlet API提供以下事件對象:javax.servlet.AsyncEvent - 在ServletRequest(通過調用ServletRequest#startAsync或ServletRequest#startAsync(ServletRequest,ServletResponse))啟動的非同步作業已完成,逾時或產生錯誤時觸發的事件。javax.servlet.http.HttpSessionBindingEvent - 將此類型的事件發送到實現HttpSessionBindingListener的對象,當該對象從會話綁定或解除綁定時,或者發送到在web.xml中配置的HttpSessionAttributeListener,當綁定任何屬性時,在會話中取消綁定或替換。會話通過對HttpSession.setAttribute的調用來綁定對象,並通過調用HttpSession.removeAttribute解除對象的綁定。當對象從會話中刪除時,我們可以使用此事件進行清理活動。javax.servlet.http.HttpSessionEvent - 這是表示Web應用程式中會話更改的事件通知的類。javax.servlet.ServletContextAttributeEvent - 關於對Web應用程式的ServletContext的屬性變更的通知的事件類別。javax.servlet.ServletContextEvent - 這是關於Web應用程式的servlet上下文更改的通知的事件類別。javax.servlet.ServletRequestEvent - 此類事件表示ServletRequest的生命週期事件。事件的原始碼是這個Web應用程式的ServletContext。javax.servlet.ServletRequestAttributeEvent - 這是事件類別,用於對應用程式中servlet請求的屬性變更的通知。
在一個web應用程式的整個運行周期內,web容器會建立和銷毀三個重要的對象,ServletContext,HttpSession,ServletRequest。
五、簡單一實例
自訂session掃描器
package com.github.listener;import java.util.Collections;import java.util.LinkedList;import java.util.List;import java.util.Timer;import javax.servlet.ServletContextEvent;import javax.servlet.http.HttpSession;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;/** * 監聽器:當網站使用者量增加時,session佔用的記憶體會越來越大,這時session的管理,將會是一項很大的系統開銷,為了高效的管理session, * 我們可以寫一個監聽器,定期清理掉到期的session * * @author raosiwen * @date 2018年6月4日 下午5:20:31 */public class SessionScanerListener implements HttpSessionListener {// 建立一個安全執行緒的集合,用來儲存sessionList<HttpSession> sessionList = Collections.synchronizedList(new LinkedList<HttpSession>());private Object lock = new Object();@Overridepublic void sessionCreated(HttpSessionEvent se) {System.out.println("session 建立成功...");HttpSession httpSession = se.getSession();synchronized (lock) {sessionList.add(httpSession);}}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {System.out.println("session 銷毀成功...");}// web應用關閉時觸發contextDestroyed事件public void contextDestroyed(ServletContextEvent servletContextEvent) {System.out.println("web應用關閉...");}// web應用啟動時觸發contextInitialized事件public void contextInitialized(ServletContextEvent servletContextEvent) {System.out.println("web應用初始化...");// 建立定時器Timer timer = new Timer();// 每隔30秒就定時執行任務timer.schedule(new MyTask(sessionList, lock), 0, 1000 * 30);}}
package com.github.listener;import java.util.List;import java.util.ListIterator;import java.util.TimerTask;import javax.servlet.http.HttpSession;/** * 定時器,定義定時任務的具體內容 * * @author raosiwen * @date 2018年6月4日 下午5:52:24 */public class MyTask extends TimerTask {private List<HttpSession> list;// 儲存傳遞過來的鎖private Object lock;// 構造方法MyTask(List<HttpSession> list, Object lock) {this.list = list;this.lock = lock;}@Overridepublic void run() {// 考慮到多線程的情況,這裡必須要同步synchronized (lock) {System.err.println("定時器開始執行");ListIterator<HttpSession> listIterator = list.listIterator();while (listIterator.hasNext()) {HttpSession httpSession = listIterator.next();// httpSession.getLastAccessedTime() = session的最後訪問時間if (System.currentTimeMillis() - httpSession.getLastAccessedTime() >= 1000 * 30) {// 手動銷毀sessionhttpSession.invalidate();// 從集合中移除已經被銷毀的sessionlistIterator.remove();}}}}}
在web.xml 中添加上下文配置
<!--spring 監聽器的配置,用於監聽session 記憶體的情況 --><listener><listener-class>com.github.listener.SessionScanerListener</listener-class></listener>
六、總結
至此,通過對servlet 的listener 案例分析以及簡單範例,可以知道listener 和servlet、filter有個共同點,都是由容器進行調度。我們只需要編寫自己的listener去實現我們關心的監聽器介面並註冊,剩下來的工作就是在我們自己的listener裡面編寫商務邏輯。
參考:https://www.cnblogs.com/EasonJim/p/7100750.html 和 52891311
Java之Listener--監控Servlet 的每個動作