程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java Web基礎知識之Listener:監控Servlet的每個動作

Java Web基礎知識之Listener:監控Servlet的每個動作

編輯:JAVA綜合教程

Java Web基礎知識之Listener:監控Servlet的每個動作


之前學習了很多涉及servlet的內容,本小結我們說一下監聽器,說起監聽器,編過桌面程序和手機App的都不陌生,常見的套路都是拖一個控件,然後給它綁定一個監聽器,即可以對該對象的事件進行監聽以便發生響應,從本質上來說這些都是觀察者模式的具體實現,在web程序中的監聽器也不例外。

在Java Web程序中使用監聽器可以通過以下兩種方法:

通過注解@WebListener來標識一個自定義的監聽器;

 

@WebListener
public class CustomListener implements Listener {
	
}
通過在web.xml中配置來使用監聽器;

	org.springframework.web.context.ContextLoaderListener

 

在Java Web程序中,主要是對ServletContext、HttpSession和ServletRequest三個對象提供支持。

一、 ServletContext監聽器

有兩類監聽器用來監聽ServletContext對象: ServletContextListener:該監聽器用來監聽ServletContext的初始化和銷毀事件;ServletContextAttributeListener:該監聽器用來監聽ServletContext中屬性的刪除、添加和替換事件; 實例如下:
@WebListener
public class MyServletListener implements ServletContextListener, ServletContextAttributeListener {
	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("ServletContextEvent source: " + sce.getSource());
		System.out.println("context created");
	}

	public void contextDestroyed(ServletContextEvent sce) {
		System.out.println("ServletContextEvent source: " + sce.getSource());
		System.out.println("context destroyed");
	}
	
	public void attributeAdded(ServletContextAttributeEvent event) {
		System.out.println("ServletContextAttributeEvent source: " + event.getSource());
		System.out.println("attribute added:  " + event.getName() + "--" + event.getValue());
	}

	public void attributeRemoved(ServletContextAttributeEvent event) {
		System.out.println("ServletContextAttributeEvent source: " + event.getSource());
		System.out.println("attribute removed:  "+ event.getName() + "--" + event.getValue());
	}

	public void attributeReplaced(ServletContextAttributeEvent event) {
		System.out.println("ServletContextAttributeEvent source: " + event.getSource());
		System.out.println("attribute replaced:  "+ event.getName() + "--" + event.getValue());
	}
}
該類實現了上述的兩個接口,啟動Servlet容器,分析結果,輸出如下: \ 可以看到在啟動的過程中首先創建了ServletContext,這激發了contextInitialized監聽器方法,然後調用了每一個Servlet的init()方法對每一個Servlet進行初始化,對所有的Servlet(這裡有兩個自定義Servlet,MyServlet,TestServlet)初始化完成後則會向ServletContext添加一個屬性,不管是事件對象還是添加的屬性都是Servlet容器提供的。 當關閉Servlet容器的時候,分析結果,輸出如下: \ 首先會調用所有Servlet的destroy方法,然後銷毀ServletContext實例。 在對屬性進行增加、替換、刪除時,會調用相應的監聽器,代碼如下:
ServletContext context = req.getServletContext();
context.setAttribute("country", "zn");
context.setAttribute("country", "en");
context.removeAttribute("country");
代碼輸出: \ 這裡要注意的是:當替換屬性時也是使用setAttribute(),只不過是key的值是相同的。當替換屬性後,監聽器得到的值是舊值(我覺得舊值和新值都應提供相應API)。

二、 HttpSession監聽器

有四類監聽器用來監聽HttpSession對象: HttpSessionListener:用來監聽HttpSession的創建和銷毀;HttpSessionActivationListener:這個監聽器是在分布式環境下使用的,當HttpSession實例要在不同的虛擬機之間進行遷移或者序列化時則會用到這個類,當HttpSession對象被遷移到新的虛擬機上時或者反序列化時則會調用sessionDidActivate()方法,當HttpSession實例從舊的虛擬機遷出或者序列化時則會調用sessionWillPassivate()方法;HttpSessionAttributeListener:用來監聽HttpSession中屬性的刪除、增加和替換事件;HttpSessionBindingListener: 其實在新出來的Servlet3.1規范中還有一個Session監聽器,javax.servlet.http.HttpSessionIdListener用於監聽SessionId的改變,由於我用的是Tomcat7還不支持Servlet3.1所以會找不到這個類,所以這裡就沒寫,如果有興趣的可以自己嘗試一下。

1、 HttpSessionListener和HttpSessionAttributeListener:

@WebListener
public class MyHttpSessionListener implements HttpSessionListener, HttpSessionAttributeListener {
	private Long startTime;
	
	public void attributeAdded(HttpSessionBindingEvent event) {
		startTime = (Long) event.getSession().getAttribute("startTime");
		
		System.out.println("HttpSessionBindingEvent source: " + event.getSource());
		System.out.println("add attribute: " + event.getName() + "--" + event.getValue());
	}

	public void attributeRemoved(HttpSessionBindingEvent event) {
		System.out.println("HttpSessionBindingEvent source: " + event.getSource());
		System.out.println("remove attribute: " + event.getName() + "--" + event.getValue());
	}

	public void attributeReplaced(HttpSessionBindingEvent event) {
		System.out.println("HttpSessionBindingEvent source: " + event.getSource());
		System.out.println("replace attribute: " + event.getName() + "--" + event.getValue());
	}

	public void sessionCreated(HttpSessionEvent se) {
		System.out.println("HttpSessionEvent source: " + se.getSource());
		System.out.println("session id: " + se.getSession().getId());
		System.out.println("session create");
	}

	public void sessionDestroyed(HttpSessionEvent se) {
		System.out.println("HttpSessionEvent source: " + se.getSource());
		System.out.println("session id: " + se.getSession().getId());
		System.out.println("active time: " + (System.nanoTime() - startTime));
		System.out.println("session destroy");
	}

}
對應的Servlet如下:
@WebServlet(name="myHttpSessionServlet", urlPatterns="/myHttpSessionServlet", loadOnStartup=1)
public class MyHttpSessionServlet extends HttpServlet {
	private static final long serialVersionUID = 5687825632187950599L;
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		HttpSession session = req.getSession();
		session.setAttribute("startTime", System.nanoTime());
		
		session.setAttribute("visitTimes", 0);
		session.setAttribute("visitTimes", 1);
		session.removeAttribute("visitTimes");
		
		session.setMaxInactiveInterval(10);
		resp.getWriter().write("session listene demo");
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
	}
}
第一次請求該網站的資源時,輸出如下: \ 可以看出第一次首先創建HttpSession,調用了sessionCreated()方法。之後是添加方法時觸發了對應的屬性監聽器,替換屬性時還是監聽器返回的替換前的值。
由於設置了存活時間,之後會觸發銷毀Session的方法,輸出如下: \ 從中可見,Session銷毀時不是只是調用sessionDestroyed()方法,在執行完這個方法之後,還會調用attributeRemoved()方法,將其中保存的所有屬性全部清除。

2、 HttpSessionActivationListener

該監聽器就像上面說過的一樣,是在分布式環境下或者當要序列HttpSession實例時才起作用。 由於Session是存儲在內存中的,所以有時候服務器為了節省內存,會把訪問較少的Session 持久化到存儲設備中,包括文件系統和數據庫,這樣就能節省內存空間,另一方面也告訴我們不要在Session中放過多的數據,以免造成服務器的負擔;當在分布式環境下時,如果一台機器發生宕機不能訪問,可以將Session序列化進行傳輸到別的服務器中的JVM中進行工作,這其中就要求存儲在Session中對數據要可以序列化,這時候是會使用到這個監聽器的;對Session進行序列化一般是Servlet容器進行的,我們可以進行配置; ERROR,錯誤使用:
HttpSession session = req.getSession();
session.setAttribute("startTime", System.nanoTime());
session.setAttribute("visitTimes", 0);
session.setAttribute("visitTimes", 1);
session.removeAttribute("visitTimes");
ObjectOutputStream output =new ObjectOutputStream(new FileOutputStream("C:\\demo"));
output.writeObject(session);
上述是錯誤的使用辦法,不能序列化HttpSession,此處的序列化只是將Session實例中的信息轉換成能在網絡中傳輸的數據形式即可,含義范圍要大於Java中的序列化概念,一般的理解是將對象轉化成字符串。 至於Session為什麼序列化,這就很明顯了,如果服務器宕機,客戶訪問服務器的所有信息因為存在於內存中,所以也就全部丟失了,將Session序列化到存儲設備之後可以在服務器恢復運行之後將用戶信息也同時恢復。

3、 HttpSessionBindingListener

首先這個監聽器的作用和之前的HttpSessionAttributeListener很像,使用起來也很類似,但是它們有如下區別: HttpSessionAttributeListener要進行注冊,或者通過注解或者通過web.xml,它是用來監聽整個應用程序中的所有的Session實例,和所有Session實例對應的屬性,屬於一個通用的監聽器;HttpSessionBindingListener則是針對於一個特定的Session和一個特定的屬性的監聽器,是量身定制的,並且它也不需要進行注冊; 使用實例如下:
public class Person implements Serializable, HttpSessionBindingListener{
	private static final long serialVersionUID = -4972586177179694554L;
	private String name;
	private Integer age;
	
	public Person(){}

	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	public void valueBound(HttpSessionBindingEvent event) {
		System.out.println("person bound");
	}
	public void valueUnbound(HttpSessionBindingEvent event) {
		System.out.println("person unbound");
	}
向doGet()方法中的Session中添加屬性:
session.setAttribute("person", new Person("lmy", 23));
session.setMaxInactiveInterval(10);
發出請求之後,輸出如下:
HttpSessionBindingEvent source: org.apache.catalina.session.StandardSessionFacade@fdfaab5
person bound
10秒之後Session失效,輸出:
HttpSessionBindingEvent source: org.apache.catalina.session.StandardSessionFacade@fdfaab5
person unbound

三、 ServletRequest監聽器

有三類監聽器用來監聽ServletRequest對象: ServletRequestListener:用來監聽ServletRequest的創建和銷毀,注意Servlet容器是通過池來重用ServletRequest的,創建一個ServletRequest實例其實就是從池中取出一個實例,銷毀該對象也就是將該實例放回池中;ServletRequestAttributeListener:用來監聽ServletRequest中屬性的刪除、增加和替換事件;AsyncListener:這個後面提到異步編程時再詳細說; 監聽器代碼如下:
@WebListener
public class MyServletRequestListener implements ServletRequestListener, ServletRequestAttributeListener {

	public void attributeAdded(ServletRequestAttributeEvent srae) {
		System.out.println("ServletRequestAttributeEvent source: " + srae.getSource());
		System.out.println("request attribute added:  " + srae.getName() + "--" + srae.getValue());
	}

	public void attributeRemoved(ServletRequestAttributeEvent srae) {
		System.out.println("ServletRequestAttributeEvent source: " + srae.getSource());
		System.out.println("request attribute removed:  " + srae.getName() + "--" + srae.getValue());
	}

	public void attributeReplaced(ServletRequestAttributeEvent srae) {
		System.out.println("ServletRequestAttributeEvent source: " + srae.getSource());
		System.out.println("request attribute replaced:  " + srae.getName() + "--" + srae.getValue());
	}

	public void requestDestroyed(ServletRequestEvent sre) {
		System.out.println("ServletRequestEvent source: " + sre.getSource());
		System.out.println("request destroy");
	}

	public void requestInitialized(ServletRequestEvent sre) {
		System.out.println("ServletRequestEvent source: " + sre.getSource());
		System.out.println("request init");
	}
}
MyHttpServlet1類接受請求代碼:

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	System.out.println("MyHttpServlet1 request work");
	System.out.println("current thread :" + Thread.currentThread().getName());
}

MyHttpServlet類接受請求代碼:

@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyHttpServlet request work");  System.out.println("current thread :" + Thread.currentThread().getName()); }

向MyHttpServlet發送一個請求輸出如下:
\
可以看出接收一個請求後首先初始化ServletRequest對象,調用requestInitialized()方法,之後會發現進行了屬性的替換,使能異步處理,在之後進行處理請求的具體工作,最後銷毀該請求對象。
向MyHttpServlet1發送一個請求輸出如下:
\
處理的流程和向MyHttpServlet發送請求相同但是看出來請求的處理線程是不同的。
在請求的過程中添加屬性和ServletContext中的對屬性的監聽器是類似的:

req.setAttribute("area", "zh");
req.setAttribute("area", "ts");
req.removeAttribute("area");
輸出如下: \ 屬性替換時也是返回的替換之前的值,和ServletContext中屬性的替換是相同的。  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved