實作 Filter
介面是定義 Filter 服務的方式,然而,在 Servlet 4.0 中,新增了 GenericFilter
類別,目的類似於 GenericServlet
,GenericFilter
將 FilterConfig
的設定、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
,並重新定義 HttpServletRequest
、HttpServletResponse
版本的 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()
的實作中,先記錄目前的系統時間,接著呼叫 FilterChain
的 doFilter()
繼續接下來的過濾器或 Servlet,當 FilterChain
的 doFilter()
返回時,取得系統時間並減去先前記錄的時間,就是請求與回應間的時間差。
過濾器的設定與 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 名稱,這可以透過 @WebFilter
的 servletNames
來設定:
@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>
...
觸發過濾器的時機,預設是瀏覽器直接發出請求。如果是那些透過 RequestDispatcher
的 forward()
或 include()
的請求,設定 @WebFilter
的 dispatcherTypes
。例如:
@WebFilter(
urlPatterns={"/some"},
dispatcherTypes={
DispatcherType.FORWARD, DispatcherType.INCLUDE,
DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC
}
)
如果不設定任何 dispatcherTypes
,則預設為 REQUEST
。FORWARD
就是指透過 RequestDispatcher
的 forward()
而來的請求 可以套用過濾器。INCLUDE
就是指透過 RequestDispatcher
的 include()
而來的請求可以套用過濾器。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 中出現的先後順序,來決定過濾器的執行順序。