1. 程式人生 > >Java中Filter、Listener,攔截器的學習,listener、 filter、servlet 載入順序及其詳解

Java中Filter、Listener,攔截器的學習,listener、 filter、servlet 載入順序及其詳解

Filter

filter可認為是Servlet的一種“變種”,它主要用於對使用者請求進行預處理,也可以對HttpServletResponse進行後處理,是個典型的處理鏈。它與Servlet的區別在於:它不能直接向用戶生成響應。完整的流程是:Filter對使用者請求進行預處理,接著將請求交給Servlet進行處理並生成響應,最後Filter再對伺服器響應進行後處理。

Filter有如下幾個用處。

  • 在HttpServletRequest到達Servlet之前,攔截客戶的HttpServletRequest。
  • 根據需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和資料。
  • 在HttpServletResponse到達客戶端之前,攔截HttpServletResponse。
  • 根據需要檢查HttpServletResponse,也可以修改HttpServletResponse頭和資料。

過濾器的基本原理
在Servlet作為過濾器使用時,它可以對客戶的請求進行處理。處理完成後,它會交給下一個過濾器處理,這樣,客戶的請求在過濾鏈裡逐個處理,直到請求傳送到目標為止。例如,某網站裡有提交“修改的註冊資訊”的網頁,當用戶填寫完修改資訊並提交後,伺服器在進行處理時需要做兩項工作:判斷客戶端的會話是否有效;對提交的資料進行統一編碼。這兩項工作可以在由兩個過濾器組成的過濾鏈裡進行處理。當過濾器處理成功後,把提交的資料傳送到最終目標;如果過濾器處理不成功,將把檢視派發到指定的錯誤頁面。

下面先介紹一個簡單的記錄日誌的Filter,這個Filter負責攔截所有的使用者請求,並將請求的資訊記錄在日誌中。

<span style="font-size:18px;">程式碼 

public class LogFilter implements Filter 
{
//FilterConfig可用於訪問Filter的配置資訊
private FilterConfig config;
//實現初始化方法
public void init(FilterConfig config)
{
this.config = config; 
}
//實現銷燬方法
public void destroy()
{
this.config = null; 
}
//執行過濾的核心方法
public void doFilter(ServletRequest request,ServletResponse response, FilterChain chain)throws IOException,ServletException
{
//---------下面程式碼用於對使用者請求執行預處理---------
//獲取ServletContext物件,用於記錄日誌
ServletContext context = this.config.getServletContext(); 
long before = System.currentTimeMillis();
System.out.println("開始過濾...");
//將請求轉換成HttpServletRequest請求
HttpServletRequest hrequest = (HttpServletRequest)request;
//記錄日誌
context.log("Filter已經截獲到使用者的請求地址: " + hrequest.getServletPath());
//Filter只是鏈式處理,請求依然放行到目的地址
chain.doFilter(request, response); 
//---------下面程式碼用於對伺服器響應執行後處理---------
long after = System.currentTimeMillis();
//記錄日誌
context.log("過濾結束");
//再次記錄日誌
context.log("請求被定位到" + hrequest.getRequestURI() + "所花的時間為: " + (after - before)); 
}
}</span>

上面程式實現了doFilter()方法,實現該方法就可實現對使用者請求進行預處理,也可實現對伺服器響應進行後處理——它們的分界線為是否呼叫了chain.doFilter(),執行該方法之前,即對使用者請求進行預處理;執行該方法之後,即對伺服器響應進行後處理。

Filter生命週期同Servlet 的生命週期一樣

    (1) 初始化
  在下列時刻裝入 Servlet:
 如果已配置自動裝入選項,則在啟動伺服器時自動裝入
 在伺服器啟動後,客戶機首次向 Servlet 發出請求時
 重新裝入 Servlet 時裝入 Servlet 後,伺服器建立一個 Servlet 例項並且呼叫 Servlet 的 init() 方法。在初始化階段,Servlet 初始化引數被傳遞給 Servlet 配置物件。
  (2) 請求處理
  對於到達伺服器的客戶機請求,伺服器建立特定於請求的一個“請求”物件和一個“響應”物件。伺服器呼叫 Servlet 的 service() 方法,該方法用於傳遞“請求”和“響應”物件。service() 方法從“請求”物件獲得請求資訊、處理該請求並用“響應”物件的方法以將響應傳回客戶機。service() 方法可以呼叫其它方法來處理請求,例如 doGet()、doPost() 或其它的方法。

   (3) 終止
  當伺服器不再需要 Servlet, 或重新裝入 Servlet 的新例項時,伺服器會呼叫 Servlet 的 destroy() 方法。

Listener

它是基於觀察者模式設計的,Listener 的設計對開發 Servlet 應用程式提供了一種快捷的手段,能夠方便的從另一個縱向維度控制程式和資料。目前 Servlet 中提供了 5 種兩類事件的觀察者介面,它們分別是:4 個 EventListeners 型別的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 個 LifecycleListeners 型別的,ServletContextListener、HttpSessionListener。如下圖所示:

Listener是Servlet的監聽器,它可以監聽客戶端的請求、服務端的操作等。通過監聽器,可以自動激發一些操作,比如監聽線上的使用者的數量。當增加一個HttpSession時,就激發sessionCreated(HttpSessionEvent se)方法,這樣就可以給線上人數加1。常用的監聽介面有以下幾個:

ServletContextAttributeListener監聽對ServletContext屬性的操作,比如增加、刪除、修改屬性。

ServletContextListener監聽ServletContext。當建立ServletContext時,激發contextInitialized(ServletContextEvent sce)方法;當銷燬ServletContext時,激發contextDestroyed(ServletContextEvent sce)方法。

HttpSessionListener監聽HttpSession的操作。當建立一個Session時,激發session Created(HttpSessionEvent se)方法;當銷燬一個Session時,激發sessionDestroyed (HttpSessionEvent se)方法。

HttpSessionAttributeListener監聽HttpSession中的屬性的操作。當在Session增加一個屬性時,激發attributeAdded(HttpSessionBindingEvent se) 方法;當在Session刪除一個屬性時,激發attributeRemoved(HttpSessionBindingEvent se)方法;當在Session屬性被重新設定時,激發attributeReplaced(HttpSessionBindingEvent se) 方法。

下面我們開發一個具體的例子,這個監聽器能夠統計線上的人數。在ServletContext初始化和銷燬時,在伺服器控制檯列印對應的資訊。當ServletContext裡的屬性增加、改變、刪除時,在伺服器控制檯列印對應的資訊。

要獲得以上的功能,監聽器必須實現以下3個介面:

HttpSessionListener

ServletContextListener

ServletContextAttributeListener

例子:

package com.ee.listener;  
  
import javax.servlet.ServletContextAttributeEvent;  
import javax.servlet.ServletContextAttributeListener;  
import javax.servlet.ServletContextEvent;  
import javax.servlet.ServletContextListener;  
import javax.servlet.http.HttpSessionEvent;  
import javax.servlet.http.HttpSessionListener;  
  
/** 
 * @author Administrator 
 * 
 */  
public class OnlineUserListener implements HttpSessionListener,  
        ServletContextListener, ServletContextAttributeListener {  
    private long onlineUserCount = 0;  
  
    public long getOnlineUserCount() {  
        return onlineUserCount;  
    }  
  
    /* (non-Javadoc) 
     * @see javax.servlet.ServletContextAttributeListener#attributeAdded(javax.servlet.ServletContextAttributeEvent) 
     */  
    @Override  
    public void attributeAdded(ServletContextAttributeEvent arg0) {  
  
    }  
  
    /* (non-Javadoc) 
     * @see javax.servlet.ServletContextAttributeListener#attributeRemoved(javax.servlet.ServletContextAttributeEvent) 
     */  
    @Override  
    public void attributeRemoved(ServletContextAttributeEvent arg0) {  
  
    }  
  
    /* (non-Javadoc) 
     * @see javax.servlet.ServletContextAttributeListener#attributeReplaced(javax.servlet.ServletContextAttributeEvent) 
     */  
    @Override  
    public void attributeReplaced(ServletContextAttributeEvent attributeEvent) {  
        System.err.println("...attributeReplaced...");  
    }  
  
    /* (non-Javadoc) 
     * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent) 
     */  
    @Override  
    public void contextDestroyed(ServletContextEvent arg0) {  
  
    }  
  
    /* (non-Javadoc) 
     * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent) 
     */  
    @Override  
    public void contextInitialized(ServletContextEvent arg0) {  
  
    }  
  
    /* (non-Javadoc) 
     * @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent) 
     */  
    @Override  
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {  
        onlineUserCount ++;  
        toUpdateCount(httpSessionEvent);  
    }  
  
    /* (non-Javadoc) 
     * @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent) 
     */  
    @Override  
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {  
        onlineUserCount --;  
        toUpdateCount(httpSessionEvent);  
    }  
  
    private void toUpdateCount(HttpSessionEvent httpSessionEvent){  
        httpSessionEvent.getSession().setAttribute("onlineUserCount", onlineUserCount);  
    }  
}  

在專案中總會遇到一些關於載入的優先順序問題,近期也同樣遇到過類似的,所以自己查詢資料總結了下,下面有些是轉載其他人的,畢竟人家寫的不錯,自己也就不重複造輪子了,只是略加點了自己的修飾。
首先可以肯定的是,載入順序與它們在 web.xml 檔案中的先後順序無關。即不會因為 filter 寫在 listener 的前面而會先載入 filter。最終得出的結論是:listener -> filter -> servlet

        同時還存在著這樣一種配置節:context-param,它用於向 ServletContext 提供鍵值對,即應用程式上下文資訊。我們的 listener, filter 等在初始化時會用到這些上下文中的資訊,那麼 context-param 配置節是不是應該寫在 listener 配置節前呢?實際上 context-param 配置節可寫在任意位置,因此真正的載入順序為:context-param -> listener -> filter -> servlet

攔截器

攔截器,在AOP(Aspect-Oriented Programming)中用於在某個方法或欄位被訪問之前,進行攔截然後在之前或之後加入某些操作。攔截是AOP的一種實現策略。
在Webwork的中文文件的解釋為——攔截器是動態攔截Action呼叫的物件。它提供了一種機制可以使開發者可以定義在一個action執行的前後執行的程式碼,也可以在一個action執行前阻止其執行。同時也是提供了一種可以提取action中可重用的部分的方式。
談到攔截器,還有一個詞大家應該知道——攔截器鏈(Interceptor Chain,在Struts 2中稱為攔截器棧 Interceptor Stack)。攔截器鏈就是將攔截器按一定的順序聯結成一條鏈。在訪問被攔截的方法或欄位時,攔截器鏈中的攔截器就會按其之前定義的順序被呼叫。

攔截器的實現原理:
大部分時候,攔截器方法都是通過代理的方式來呼叫的。Struts 2的攔截器實現相對簡單。當請求到達Struts 2的ServletDispatcher時,Struts 2會查詢配置檔案,並根據其配置例項化相對的攔截器物件,然後串成一個列表(list),最後一個一個地呼叫列表中的攔截器。

攔截器與過濾器的區別 :

1. 攔截器是基於java的反射機制的,而過濾器是基於函式回撥。
2. 攔截器不依賴與servlet容器,過濾器依賴與servlet容器。
3. 攔截器只能對action請求起作用,而過濾器則可以對幾乎所有的請求起作用。

4. 攔截器可以訪問action上下文、值棧裡的物件,而過濾器不能訪問。

5. 在action的生命週期中,攔截器可以多次被呼叫,而過濾器只能在容器初始化時被呼叫一次