Filter 的實作與設定


實作 Filter 介面是定義 Filter 服務的方式,然而,在 Servlet 4.0 中,新增了 GenericFilter 類別,目的類似於 GenericServletGenericFilterFilterConfig 的設定、Filter 初始參數的取得做了封裝,來看看它的原始碼:

package javax.servlet;

import java.io.Serializable;
import java.util.Enumeration;

public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
    private static final long serialVersionUID = 1L;

    private volatile FilterConfig filterConfig;

    @Override
    public String getInitParameter(String name) {
        return getFilterConfig().getInitParameter(name);
    }

    @Override
    public Enumeration<String> getInitParameterNames() {
        return getFilterConfig().getInitParameterNames();
    }

    public FilterConfig getFilterConfig() {
        return filterConfig;
    }

    @Override
    public ServletContext getServletContext() {
        return getFilterConfig().getServletContext();
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig  = filterConfig;
        init();
    }

    public void init() throws ServletException {
    }
    
    @Override
    public String getFilterName() {
        return getFilterConfig().getFilterName();
    }
}

因此若是 GenericFilter 的子類別,要定義 Filter 的初始化,可以重新定義無參數 init() 方法了,Servlet 4.0 之中,也新增了 HttpFilter,繼承自 GenericFilter,對於 HTTP 方法的處理,新增了另一個版本的 doFilter() 方法:

package javax.servlet.http;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.GenericFilter;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract class HttpFilter extends GenericFilter {
    private static final long serialVersionUID = 1L;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (!(request instanceof HttpServletRequest)) {
            throw new ServletException(request + " not HttpServletRequest");
        }
        if (!(response instanceof HttpServletResponse)) {
            throw new ServletException(request + " not HttpServletResponse");
        }
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }

    protected void doFilter(HttpServletRequest request, HttpServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }
}

因此,在 Servlet 4.0 中,若要定義 Filter,可以繼承 HttpFilter,並重新定義 HttpServletRequestHttpServletResponse 版本的 doFilter() 方法。

例如,以下實作一個簡單的效能量測過濾器,可用來記錄請求與回應間的時間差,了解Servlet處理請求到回應所需花費的時間。

package cc.openhome;

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebFilter("/*")
public class PerformanceFilter extends HttpFilter {
    @Override
    protected void doFilter(
         HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws IOException, ServletException {
         long begin = System.currentTimeMillis();
            chain.doFilter(request, response);
            getServletContext().log("Request process in " +
                    (System.currentTimeMillis() - begin) + " milliseconds");
    }
}

當過濾器類別被載入容器時並實例化後,容器會執行其 init() 方法並傳入 FilterConfig 物件作為參數,呼叫無參數 init() 方法。

當請求來到 Filter,會呼叫 Filter 介面的 doFilter(),若是 HttpFilter 的子類別,就會進一步呼叫有 HttpServletRequest 參數的 doFilter() 方法。

doFilter() 的實作中,先記錄目前的系統時間,接著呼叫 FilterChaindoFilter() 繼續接下來的過濾器或 Servlet,當 FilterChaindoFilter() 返回時,取得系統時間並減去先前記錄的時間,就是請求與回應間的時間差。

過濾器的設定與 Servlet 的設定很類似。@WebFilter 中的 filterName 設定過濾器名稱,預設值是類別完全吻合名稱,urlPatterns 設定哪些 URL 請求必須套用哪個過濾器,可套用的 URL 模式與 Servlet 基本上相同,而 /* 表示套用在所有的 URL 請求。

如果要在 web.xml 中設定,則可以如下:

<web-app ...>
    <filter>
        <filter-name>performance</filter-name>
        <filter-class>cc.openhome.PerformanceFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>performance</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    // 略...
</web-app>

<filter> 標籤中使用 <filter-name><filter-class> 設定過濾器名稱與類別名稱。而在 <filter-mapping> 中,則用 <filter-name><url-pattern> 來設定哪些 URL 請求必須套用哪個過濾器。

在過濾器的請求套用上,除了指定 URL 模式之外,你也可以指定 Servlet 名稱,這可以透過 @WebFilterservletNames 來設定:

@WebFilter(servletNames={"SomeServlet"})

或 web.xml 中,在 <filter-mapping> 中使用 <servlet-name> 來設定:

<filter-mapping>
    <filter-name>performance</filter-name>
    <servlet-name>SomeServlet</servlet-name>
</filter-mapping>

如果想一次符合所有的 Servlet 名稱,則可以使用星號(*)。如果在過濾器初始化時,想要讀取一些參數,可以在 @WebFilter 中使用 @WebInitParam 設定 initParams。例如:

...
@WebFilter(
    urlPatterns={"/*"}, 
    initParams={
        @WebInitParam(name = "PARAM1", value = "VALUE1"),
        @WebInitParam(name = "PARAM2", value = "VALUE2")
    }
)
public class PerformanceFilter extends HttpFilter {
    private String PARAM1;
    private String PARAM2;

    @Override
    public void init() throws ServletException {
        PARAM1 = getInitParameter("PARAM1");
        PARAM2 = getInitParameter("PARAM2");
    }
   ...
}

若要在 web.xml 中設定過濾器的初始參數,可以在 <filter> 標籤之中,使用 <init-param> 進行設定, web.xml 中的設定會覆蓋標註的設定。例如:

...
    <filter>
        <filter-name>PerformanceFilter</filter-name>
        <filter-class>cc.openhome.PerformanceFilter</filter-class>
        <init-param>
            <param-name>PARAM1</param-name>
            <param-value>VALUE1</param-value>
        </init-param>
        <init-param>
            <param-name>PARAM2</param-name>
            <param-value>VALUE2</param-value>
        </init-param>
    </filter>
...

觸發過濾器的時機,預設是瀏覽器直接發出請求。如果是那些透過 RequestDispatcherforward()include() 的請求,設定 @WebFilterdispatcherTypes。例如:

@WebFilter(
    urlPatterns={"/some"}, 
    dispatcherTypes={
        DispatcherType.FORWARD, DispatcherType.INCLUDE, 
        DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC
    }
)

如果不設定任何 dispatcherTypes,則預設為 REQUESTFORWARD 就是指透過 RequestDispatcherforward() 而來的請求 可以套用過濾器。INCLUDE 就是指透過 RequestDispatcherinclude() 而來的請求可以套用過濾器。ERROR 是指由容器處理 例外而轉發過來的請求可以觸發過濾器。ASYNC 是指非同步處理的請求可以觸發過濾器(之後還會說明非同步處理)。

若要在 web.xml 中設定,則可以使用 <dispatcher>標籤。例如:

...
<filter-mapping>
    <filter-name>SomeFilter</filter-name>
    <servlet-name>*.do</servlet-name>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
    <dispatcher>ASYNC</dispatcher>
</filter-mapping>
...

你可以透過 <url-pattern><servlet-name> 來指定,哪些 URL 請求或哪些 Servlet 可套用過濾器。如果同時具備 <url-pattern><servlet-name>,則先比對 <url-pattern>,再比對 <servlet-name>。如果有某個 URL 或 Servlet 會套用多個過濾器,則根據 <filter-mapping> 在 web.xml 中出現的先後順序,來決定過濾器的執行順序。