Java之Listener--時刻監控着Servlet 的一舉一動


一、背景

每次啟動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對象
	 */
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		// TODO Auto-generated method stub
		LOG.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對象
	 */
	@Override
	public 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
 * @date 2018年6月4日 下午5:20:31
 */
public class SessionScanerListener implements HttpSessionListener {
	// 創建一個線程安全的集合,用來存儲session
	List<HttpSession> sessionList = Collections.synchronizedList(new LinkedList<HttpSession>());

	private Object lock = new Object();

	@Override
	public void sessionCreated(HttpSessionEvent se) {
		System.out.println("session 創建成功...");
		HttpSession httpSession = se.getSession();
		synchronized (lock) {
			sessionList.add(httpSession);
		}
	}

	@Override
	public 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;

/**
 * 定時器,定義定時任務的具體內容
 * @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;
	}

	@Override
	public 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) {
					// 手動銷毀session
					httpSession.invalidate();
					// 從集合中移除已經被銷毀的session
					listIterator.remove();
				}
			}
		}
	}
}

在web.xml 中添加上下文配置

<!--spring 監聽器的配置,用於監聽session 內存的情況 -->
	<listener>
		<listener-class>com.github.listener.SessionScanerListener</listener-class>
	</listener>

servlet 中常用的listener:

<!-- 此監聽器主要用於解決java.beans.Introspector導致的內存泄漏的問題,有些框架(Structs,Quartz) 不會清理Introspector。(應該放在開頭部分,至少也得在ContextLoaderListener 
		之前) -->
	<listener>
		<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
	</listener>
	<!--spring 監聽器的配置,用於在啟動 Web 容器時,自動裝配 ApplicationContext 的配置信息 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!--spring 監聽器的配置,用於監聽session 內存的情況 -->
	<listener>
		<listener-class>com.github.listener.SessionScanerListener</listener-class>
	</listener>
	<!--spring log4j 監聽器 -->
	<listener>
		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
	</listener>

六、總結

至此,通過對servlet 的listener 案例分析以及簡單樣例,可以知道listener 和filter 有個共同點,都是由容器進行調度。我們只需要編寫自己的listener 去實現我們關心的監聽器接口並注冊,剩下來的工作就是在我們自己的listener 里面編寫業務邏輯。

 

參考:https://www.cnblogs.com/EasonJim/p/7100750.html 和 https://blog.csdn.net/reggergdsg/article/details/52891311

版權聲明:本文為博主原創文章,未經博主允許不得轉載。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM