過濾器是個可重用的(Resuable)元件,可以轉換對資源的請求,也可以轉換回應的內容(過濾器並不負責建立回應內容)。
過濾器通常作為一個服務(Service)加入至應用程式之中,即時地為應用程式增加功能,但不用修改原有的應用程式,在不需要使用服務時,可以直接將過濾器從應用程式抽離,而不用修改原應用程式。過濾器可以實現的服務以下有幾個例子:
效能量測
例如請求與回應之間的時間差。
請求內容的過濾、轉換
特定字元的過濾、標頭、請求參數的轉換等。
使用者的驗證
驗證使用者是否登入、是否具備某種身份、是否來目某些區域。
資源存取限制
根據使用者等資訊,確認其是否可存取某些資源。
回應的加工
針對回應作字元轉換、資料壓縮等動作。
Servlet╱JSP 提供了過濾器機制讓你實作這些元件服務,而就如下圖所示的,你可以視需求抽換過濾器或調整過濾器的順序,也可以針對不同的 URL 套用不同的過濾器。甚至在不同的 Servlet 間請求轉發或包括時套用過濾器。
正如其名稱所示,過濾器概念上就像個濾網,需要時在某些資源存取前、回應前加上濾網,不需要時可直接將濾網拿掉。
在 Servlet╱JSP 中要實作過濾器,在 Servlet 4.0 之前,主要是實作 Filter
介面。Filter
介面定義了三個實作方法:init()
、doFilter()
與 destroy()
。
package javax.servlet;
import java.io.IOException;
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
在 Servlet 4.0 中,由於是基於 Java SE 8,因此 Filter
的 init()
與 destroy()
運用的預設方法實作,然而方法內容為空,這省去在 Servlet 3.1 或更早版本中,實作 Filter
時,即使不需要定義初始或銷毀動作,也必須定義 init()
與 destroy()
的麻煩。
Filter
介面在定義上與 Servlet
介面很類似,同樣都有 init()
與 destroy()
方法,除了 Filter
介面的 init()
方法上的參數是 FilterConfig
,而 doFilter()
多了一個 FilterChain
參數。
FilterConfig
類似於 Servlet
介面 init()
方法參數上的 ServletConfig
,FilterConfig
為實作 Filter
介面的類別上標註或 web.xml 中過濾器定義的代表物件。如果你在定義過濾器時有設定初始參數,則可以透過 FilterConfig
的 getInitParameter()
方法來取得初始參數。
Filter
介面的 doFilter()
方法則類似於 Servlet
介面的 service()
方法。當請求來到容器,而容器發現呼叫 Servlet
的 service()
方法前,可以套用某過濾器時,就會呼叫該過濾器的 doFilter()
方法。你就是在 doFilter()
方法中,進行 service()
方法的前置處理,而後決定是否呼叫 FilterChain
的 doFilter()
方法。
如果呼叫了 FilterChain
的 doFilter()
方法,就會執行下一個過濾器,如果沒有下一個過濾器了,就呼叫請求目標 Servlet
的 service()
方法。如果因為某個情況(例如使用者沒有通過驗證)而沒有呼叫 FilterChain
的 doFilter()
,則請求就不會繼續交給接下來的過濾器或目標 Servlet,這時就是所謂的攔截請求(從 Servlet 的觀點來看,它根本不知道瀏覽器有發出請求)。
FilterChain
的 doFilter()
實作,概念上類似以下:
Filter filter = filterIterator.next();
if(filter != null) {
filter.doFilter(request, response, this);
}
else {
targetServlet.service(request, response);
}
在陸續呼叫完 Filter
實例的 doFilter()
仍至 Servlet
的 service()
之後,流程會以堆疊順序返回,所以在 FilterChain
的 doFilter()
執行完畢後,就是你可以針對 service()
方法作後續處理的時機。
// service() 前置處理
chain.doFilter(request, response);
// service() 後置處理
你只需要知道 FilterChain
執行後會以堆疊順序返回即可。在實作 Filter
介面時,不用理會這個 Filter
前後是否有其他 Filter
,應該將之作為一個獨立的元件設計,是否採用 Filter
,都不應影響應用程式的基本行為。
如果在呼叫 Filter
的 doFilter()
期間,因故丟出 UnavailableException
,此時不會繼續下一個 Filter
,容器可以檢驗例外的 isPermanent()
,如果不是 true
,則可以在稍後重試 Filter
。