[Servlet] Filter - 強大的關卡控管人


寫過Web的人都有這種經驗,有些頁面在顯示內容的時候會希望先做過過濾再決定輸出內容(例如有些頁面只有登入著才能夠看到),或者希望記錄那一頁的點擊率。這些我們都能夠直接刻在Servlet裡面來達到,但是如果你有20個Servlet都需要驗證,難道那20個裡面都寫一樣的驗證邏輯(或者呼叫一樣的驗證邏輯)?

今天我們要來看看Servlet裡面的一個很強大的功能,能夠輕鬆解決這個問題,而它就是Filter。

前言

寫過Web的人都有這種經驗,有些頁面在顯示內容的時候會希望先做過過濾再決定輸出內容(例如有些頁面只有登入著才能夠看到),或者希望記錄那一頁的點擊率。這些我們都能夠直接刻在Servlet裡面來達到,但是如果你有20個Servlet都需要驗證,難道那20個裡面都寫一樣的驗證邏輯(或者呼叫一樣的驗證邏輯)?

今天我們要來看看Servlet裡面的一個很強大的功能,能夠輕鬆解決這個問題,而它就是Filter。

何為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基本結構

首先我們先看下面這個示意圖:

32192600

可以看到,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動作如下:


@Override
public 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的方式統一解決了共同頁面所需要處理的問題,因此會利用他能夠帶來很好的效果。

Dotblogs 的標籤: ,

Google+

創用 CC 授權條款
Alan Tsai 的隨手筆記Alan Tsai製作,以創用CC 姓名標示 4.0 國際 授權條款釋出。