關於過濾器


過濾器是個可重用的(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,因此 Filterinit()destroy() 運用的預設方法實作,然而方法內容為空,這省去在 Servlet 3.1 或更早版本中,實作 Filter 時,即使不需要定義初始或銷毀動作,也必須定義 init()destroy() 的麻煩。

Filter 介面在定義上與 Servlet 介面很類似,同樣都有 init()destroy() 方法,除了 Filter 介面的 init() 方法上的參數是 FilterConfig,而 doFilter() 多了一個 FilterChain 參數。

FilterConfig 類似於 Servlet 介面 init() 方法參數上的 ServletConfigFilterConfig 為實作 Filter 介面的類別上標註或 web.xml 中過濾器定義的代表物件。如果你在定義過濾器時有設定初始參數,則可以透過 FilterConfiggetInitParameter() 方法來取得初始參數。

Filter 介面的 doFilter() 方法則類似於 Servlet 介面的 service() 方法。當請求來到容器,而容器發現呼叫 Servletservice() 方法前,可以套用某過濾器時,就會呼叫該過濾器的 doFilter() 方法。你就是在 doFilter() 方法中,進行 service() 方法的前置處理,而後決定是否呼叫 FilterChaindoFilter()方法。

如果呼叫了 FilterChaindoFilter() 方法,就會執行下一個過濾器,如果沒有下一個過濾器了,就呼叫請求目標 Servletservice() 方法。如果因為某個情況(例如使用者沒有通過驗證)而沒有呼叫 FilterChaindoFilter(),則請求就不會繼續交給接下來的過濾器或目標 Servlet,這時就是所謂的攔截請求(從 Servlet 的觀點來看,它根本不知道瀏覽器有發出請求)。

FilterChaindoFilter() 實作,概念上類似以下:

Filter filter = filterIterator.next();
if(filter != null) {
    filter.doFilter(request, response, this);
} 
else {
    targetServlet.service(request, response);
}

在陸續呼叫完 Filter 實例的 doFilter() 仍至 Servletservice() 之後,流程會以堆疊順序返回,所以在 FilterChaindoFilter() 執行完畢後,就是你可以針對 service() 方法作後續處理的時機。

// service() 前置處理
chain.doFilter(request, response);
// service() 後置處理

你只需要知道 FilterChain 執行後會以堆疊順序返回即可。在實作 Filter 介面時,不用理會這個 Filter 前後是否有其他 Filter,應該將之作為一個獨立的元件設計,是否採用 Filter,都不應影響應用程式的基本行為。

關於過濾器

如果在呼叫 FilterdoFilter() 期間,因故丟出 UnavailableException,此時不會繼續下一個 Filter,容器可以檢驗例外的 isPermanent(),如果不是 true,則可以在稍後重試 Filter