在DRP項目中,多次提到了Filter,它解決了字元集的統一設定以及統一控制簡單WebCache,從中我們可以體會到,它給我們帶來的好處不僅僅是減少代碼量這麼簡單,它的出現避免了我們每個頁面重複的編寫相同的代碼,減少了我們的工作量,而且給維護帶來了極大的便利,那麼它是如何?統一管理的呢?既然它能統一管理某些重複的操作,那麼它和AOP有什麼關係呢?
Filter簡介
ServletAPI中提供了一個Filter介面,開發web應用時,如果編寫的Java類實現了這個介面,則把這個java類稱之為過濾器Filter。
通過Filter技術,開發人員可以實現使用者在訪問某個目標資源之前,對訪問的請求和響應進行攔截。簡單說,就是可以實現web容器對某資源的訪問前截獲進行相關的處理,還可以在某資源向web容器返迴響應前進行截獲進行處理。
是filter調用關係的UML圖:
一個filter必須實現javax.servlet.Filter。
三個方法
1. voidsetFilterConfig(FilterConfig config) //設定filter 的設定物件;
2. FilterConfiggetFilterConfig() //返回filter的設定物件;
3. voiddoFilter(ServletRequest req,ServletResponse res,FilterChain chain) //執行filter的工作
Filter實現攔截的原理
Filter介面中有一個doFilter方法,當開發人員編寫好Filter類實現doFilter方法,並配置對哪個web資源進行攔截後,WEB伺服器每次在調用web資源的service方法之前(伺服器內部對資源的訪問機制決定的),都會先調用一下filter的doFilter方法。
應用舉例:
大量設定請求編碼
public class EncodingFilter implements Filter { private String encoding = null; public void destroy() { encoding = null; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String encoding = getEncoding(); if (encoding == null){ encoding = "gb2312"; } request.setCharacterEncoding(encoding);// 在請求裡設定上指定的編碼 chain.doFilter(request, response); //通過控制對chain.doFilter的方法的調用,來決定是否需要訪問目標資源 } public void init(FilterConfig filterConfig) throws ServletException { this.encoding = filterConfig.getInitParameter("encoding"); } private String getEncoding() { return this.encoding; } }
xml配置代碼
<filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.logcd.filter.EncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>gb2312</param-value> </init-param> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
如上的程式碼完成的功能為,無論進入那個頁面,都要先執行EncodingFilter類的dofilter方法設定字元集
其中,doFilter()方法類似於Servlet介面的service()方法。當用戶端請求目標資源的時候,容器就會調用與這個目標資源相關聯的過濾器的doFilter()方法。
參數 request, response 為web 容器或 Filter 鏈的上一個 Filter 傳遞過來的請求和相應對象;參數 chain 代表當前 Filter 鏈的對象。
對於FilterChain介面,代表當前Filter鏈的對象。由容器實現,容器將其執行個體作為參數傳入過濾器對象的doFilter()方法中。
過濾器對象使用FilterChain對象調用過濾器鏈中的下一個過濾器,或者目標Servlet 程式去處理,也可以直接向用戶端返迴響應資訊,或者利用RequestDispatcher的forward()和include()方法,以及HttpServletResponse的sendRedirect()方法將請求轉向到其他資源。
這個方法的請求和響應參數的類型是 ServletRequest和ServletResponse,也就是說,過濾器的使用並不依賴於具體的協議。
Filter生命週期
和Servlet一樣,Filter的建立和銷毀也是由WEB伺服器負責。
與Servlet區別的是
1>在應用啟動的時候就進行裝載Filter類而servlet是在請求時才建立(但filter與Servlet的load-on-startup配置效果相同)。
2>容器建立好Filter對象執行個體後,調用init()方法。接著被Web容器儲存進應用級的集合容器中去了等待著,使用者訪問資源。
3>當使用者訪問的資源正好被Filter的url-pattern攔截時,容器會取出Filter類調用doFilter方法,下次或多次訪問被攔截的資源時,Web容器會直接取出指定Filter對象執行個體調用doFilter方法(Filter對象常駐留Web容器了)。
4>當應用服務被停止或重新裝載了,則會執行Filter的destroy方法,Filter對象銷毀。
Filter工作原理(執行流程)
當用戶端發出Web資源的請求時,Web伺服器根據應用程式設定檔設定的過濾規則進行檢查,若客戶請求滿足過濾規則,則對客戶請求/響應進行攔截,對要求標頭和請求資料進行檢查或改動,並依次通過過濾器鏈,最後把請求/響應交給請求的Web資源處理。
請求資訊在過濾器鏈中可以被修改,也可以根據條件讓請求不發往資源處理器,並直接向客戶機發回一個響應。當資源處理器完成了對資源的處理後,響應資訊將逐級逆向返回。同樣在這個過程中,使用者可以修改響應資訊,從而完成一定的任務。
過濾鏈的好處是,執行過程中任何時候都可以打斷,只要不執行chain.doFilter()就不會再執行後面的過濾器和請求的內容。
針對多個過濾器來說,例如,EncodingFilter負責設定編碼,SecurityFilter負責控制許可權,伺服器會按照web.xml中過濾器定義的先後循序組裝成一條鏈,然後一次執行其中的doFilter()方法,在實際使用時,就要特別注意過濾鏈的執行順序問題,像EncodingFilter就一定要放在所有Filter之前,這樣才能確保在使用請求中的資料前設定正確的編碼。
總結:
對於filter的應用相信大家已經明白了,它主要的作用就是使用者在訪問某個目標資源之前,對訪問的請求和響應進行攔截,做一些處理,然後再調用目標程式,這樣做的好處是可以對一些公用的操作進行抽象,就拿設定字元集來說,如果不使用這種方式,我們每個頁面都要寫設定字元集的語句。不但麻煩而且維護困難,但是如果使用filter的話,只需要添加一個類,在xml中配置一下,如果不想使用了,將設定檔中的內容去除即可。
其實這就是一種AOP(Aspect OrientedProgramming),面向切面編程。它的主要的意圖是:將日誌記錄,效能統計,安全控制,交易處理,異常處理等代碼從商務邏輯代碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導商務邏輯的方法中,進而改變這些行為的時候不影響商務邏輯的代碼。
對於設定字元集來說,它並非是商務邏輯的內容,對於這些內容的處理我們就可以提取出來,使用filter進行整體設定,這種方式相當於對類中的內容做進一步的抽象,使我們的系統更加靈活,更加能應對變化!
解疑:
由於上篇部落格介紹的動態代理,就是一種符合AOP的一種體現,現在我們又說Filter也符合AOP,那麼大家一定會有一個疑問,動態代理和Filter處理問題的區別在哪裡呢?First既然都符合AOP思想,那麼一定都可以進行統一處理(其實核心就是做進一步抽象)。那麼區別呢?
從表現形式上來說,兩者確實很相似,同樣可以在你寫的jsp、servlet代碼的前後加入其它的動作,但是兩者是有本質區別的。
1、 filter基於回呼函數,我們需要實現的filter介面中doFilter方法就是回呼函數,而動態代理則基於java本身的反射機制,如果對這種形式不瞭解,可以去看看動態代理實現過程,這是aop的基礎。這是兩者最本質的區別。
2、 filter是依賴於servlet容器的,即只能在servlet容器中執行,很顯然沒有servlet容器就無法來回調doFilter方法。而動態代理與servlet容器無關。