寫過Web的人都有這種經驗,有些頁面在顯示內容的時候會希望先做過過濾再決定輸出內容(例如有些頁面只有登入著才能夠看到),或者希望記錄那一頁的點擊率。這些我們都能夠直接刻在Servlet裡面來達到,但是如果你有20個Servlet都需要驗證,難道那20個裡面都寫一樣的驗證邏輯(或者呼叫一樣的驗證邏輯)?
今天我們要來看看Servlet裡面的一個很強大的功能,能夠輕鬆解決這個問題,而它就是Filter。
(和我部落格同時發佈:http://www.dotblogs.com.tw/alantsai/archive/2013/10/10/servlet-filter.aspx)
何為Filter
Filter其實是一個Design pattern,同時他也實現了AOP(Aspect Oriented Programming)的概念。假設今天我們有20個Servlet都只有登入過的才能夠觀看,那麼這20頁Servlet都有一個共同的Cross-cutting Concern,那就是需要先驗證此request是否已經有登入過,如果有,顯示內容。沒有,轉向登入頁面。
因此,Filter就如同他的名字一般,是一個過濾器,只有通過的才能過,沒通過的,抱歉請去另外一邊。
當然,Filter不只是可以拿來過濾request,他也可以修改request和response的內容,來達到一些目的。例如說,我們希望所有進來的request和response都使用 UTF-8作為encoding,我們就可以用Filter達到這個效果。
寫過Asp .net MVC的話,Filter就和MVC裡面的ActionFilter是一樣的概念。
Filter基本結構
首先我們先看下面這個示意圖:
可以看到,Filter就像是Client和Servlet之間的守門人,而這個守門人在那個關卡是最大的,不管是進入還是出去都要經過他,所以他可以做任何事情。例如記錄有誰進出(log 往來的request)、可以限制誰可以進入(未登入不能進入)、接受檢查違禁物品不能進入(例如過濾掉sql injection)、出來門口的時候沒收不該帶走的內容(例如給返回的圖片做浮水印)。
同時,Filter可以有很多個,又稱之為Filter Chain。所以每一個Filter可以關注在一件事情就好。
實作一個Filter
定義一個Filter一定要實作javax.Servlet.Filter 這一個interface,而此interface有3個方法要實作:
1.public void init(FilterConfig config)
2.public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
3.public void destroy()
上面各自會拋出的Exception被我忽略掉了。
和我們一般的Servlet一樣,init()和destroy()都只會執行一次,目的是初始化和釋放資源。實際的工作都在doFilter裡面。
doFilter()的參數request和response不用說,和Servlet一樣,比較特別的是他有一個FilterChain。FilterChian記錄的是下一個關卡是誰,因此它有一個方法叫做doFilter(request,response)意思是請執行下去下一個關卡。如果今天你不希望執行到下一個關卡(例如驗證沒過),可以透過request.getRequestDispatcher(). forward()的方式把它轉向。
我們說過Filter可以處理request的時候(進來)和response(要出去)的時候,而這個分水嶺就是在doFilter()的呼叫。因此:
public void doFilter(ServletRequest request, ServletResponse, FilterChain chain)
,throws ServletException, IOException
{
//這邊是在request進來做處理的地方
doFilter(request, response);
//這邊是在request出去做處理的地方
}
設定要執行Filter的Servlet對應
我們有了守關卡的人之後,當然需要告知那裡需要設立這些關卡。因此在Web.xml裡面,我們需要做一些設定。
這些設定和設定Servlet基本一樣,相信一看就懂,只是關鍵字不同:
<filter>
<filter-name>logFilter</filter-name>
<filter-class>filter.LogFilter</filter-class>
<init-param>
<param-name>firstParam</param-name>
<param-value>this is first init value</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>logFilter</filter-name>
<url-pattern>*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
這邊設定應該不需要多說,只需要注意到關於<dispatcher>的設定。
dispatcher表示在什麽情況下的Servlet才需要執行這一個filter,預設是Request,總共有以下幾個:
1.REQUEST - 直接請求符合url pattern的Servlet,此Filter生效。
2.FORWARD - 當通過某一個Servlet forward到符合url pattern的Servlet生效
3.INCLUDE - 在JSP頁面的action element <jsp:include />生效
4.ERROR - jsp 裡面用page directive指定的錯誤頁面生效
5.ASYNC - 這個是3.0才有加入的,我不是很確定不過好像是當Servlet是用Async執行的時候生效。
dispatcher可以設定多個。
Filter使用情景
Filter的建制和設定都提到了,現在給幾個簡單Filter的使用例子。
用Filter來解決編碼問題
記得之前每一次我們在Servlet輸出或取得內容都需要先設定編碼,我們可以使用Filter來一次解決編碼設定的問題,我們只需要定義一個Filter動作如下:
@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8");
chain.doFilter(request, response);
}
然後設定所有的Servlet都要使用這個filter,一切就搞定了。
用Filter轉向不同的Exception處理頁面
當我們網站出現Exception的時候,我們都希望出現一個比較友善的錯誤訊息畫面而Filter可以幫我們達到這個目的。
還記得我們說chain.doFilter()是把request傳入到下一個chain或者是Servlet,因此我們可以用它來接住任何漏掉的Exception,在轉到不同畫面:
我們的filter可以是這樣:
String message = ""; //用來儲存錯誤訊息
try{
chain.doFilter(request, response); }
catch(Exception e) {
message = e.getMessage();
}
request.setAttribute("errorMessage", message); //讓錯誤訊息可以
//帶到要顯示的頁面
//如果exception 的錯誤訊息符合某種條件,給比較specific的錯誤頁面,
//要不然就給general的
if(message.equals("exception 1")) { request.getRequestDispatcher("/customException.jsp").forward(request, response); }
else{ request.getRequestDispatcher("/exception.jsp").forward(request, response); }
這邊我就不介紹兩個jsp頁面了,反正就是把錯誤訊息用一個比較好的方式顯示出來。
使用Filter要注意的地方
因為Filter有Chain的概念,因此哪一個Filter先執行有時候有差別。例如,你有兩個Chain,一個是輸出的時候把它壓縮成為gzip,一個是給圖片加上浮水印。如果先執行了壓縮,才執行浮水印當然就會出錯。
Filter的順序,通常來說是依照他們在web.xml定義的順序去執行。
結語
Filter是一個很強大的功能,而我這邊只介紹了兩個簡單的使用情景。Filter其實可以對輸出的內容做過濾(例如給輸出圖片增加浮水印,還是給輸出的文字做過濾),而因為他在執行過程有辦法拿到request和response,因此他能做到的事情和Servlet能夠做到的基本一樣。
Filter提供給我們一種low coupling的方式統一解決了共同頁面所需要處理的問題,因此會利用他能夠帶來很好的效果。