Filter 是實作 javax.servlet.Filter 介面的 Java 類別,能在請求到達 Servlet/JSP 前、或回應離開伺服器前攔截處理。常用於編碼設定、登入驗證、日誌記錄、壓縮、XSS 防護等橫切關注點。
| 方法 | 呼叫時機 | 用途 |
|---|---|---|
init(FilterConfig config) | Web 應用啟動時,僅一次 | 讀取初始化參數、建立資源連線 |
doFilter(req, resp, chain) | 每次請求匹配時 | 核心邏輯:預處理 → chain.doFilter() → 後處理 |
destroy() | Web 應用關閉/重新部署 | 釋放資源、關閉連線 |
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDateTime;
@WebFilter("/*") // 攔截所有請求
public class LogFilter implements Filter {
@Override
public void init(FilterConfig config) {
String appName = config.getInitParameter("appName");
System.out.println("[" + LocalDateTime.now() + "] " + appName + " Filter 初始化完成");
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
long start = System.currentTimeMillis();
// 前置處理
System.out.println(">>> 請求: " + request.getMethod() + " " + request.getRequestURI()
+ " 來自 " + request.getRemoteAddr());
try {
chain.doFilter(req, resp); // 傳遞給下一個 Filter 或目標 Servlet
} finally {
// 後置處理 (確保執行)
long duration = System.currentTimeMillis() - start;
System.out.println("<<< 完成: " + request.getRequestURI() + " 耗時 " + duration + "ms");
}
}
@Override
public void destroy() {
System.out.println("LogFilter 銷毀");
}
}
<filter>
<filter-name>LogFilter</filter-name>
<filter-class>com.example.filter.LogFilter</filter-class>
<init-param>
<param-name>appName</param-name>
<param-value>MyWebApp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LogFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 也可映射到特定 Servlet -->
<filter-mapping>
<filter-name>LogFilter</filter-name>
<servlet-name>MyServlet</servlet-name>
</filter-mapping>
<filter-mapping> <filter-name>LogFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <!-- 直接請求 (預設) --> <dispatcher>FORWARD</dispatcher> <!-- RequestDispatcher.forward() --> <dispatcher>INCLUDE</dispatcher> <!-- RequestDispatcher.include() --> <dispatcher>ERROR</dispatcher> <!-- 錯誤頁面 --> <dispatcher>ASYNC</dispatcher> <!-- 非同步請求 --> </filter-mapping>
順序由 web.xml 中 <filter-mapping> 出現順序決定,或 @WebFilter 無保證順序 (需 @Order 或 web.xml 指定)。
<!-- 先執行 EncodingFilter,再執行 AuthFilter --> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
@Override public void init(FilterConfig cfg) {
String e = cfg.getInitParameter("encoding");
if (e != null) encoding = e;
}
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
req.setCharacterEncoding(encoding);
resp.setCharacterEncoding(encoding);
resp.setContentType("text/html;charset=" + encoding);
chain.doFilter(req, resp);
}
}
@WebFilter("/member/*")
public class AuthFilter implements Filter {
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest) req;
HttpServletResponse s = (HttpServletResponse) resp;
if (r.getSession().getAttribute("loginUser") == null) {
s.sendRedirect(r.getContextPath() + "/login.jsp?redirect=" + r.getRequestURI());
return;
}
chain.doFilter(req, resp);
}
}
public class XssFilter implements Filter {
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
chain.doFilter(new XssRequestWrapper((HttpServletRequest) req), resp);
}
}
// 包裝類覆寫 getParameter 等方法,對輸入進行 HTML Escape
class XssRequestWrapper extends HttpServletRequestWrapper {
// ... 實作細節略
}
public class GzipFilter implements Filter {
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest) req;
HttpServletResponse s = (HttpServletResponse) resp;
if (r.getHeader("Accept-Encoding") != null && r.getHeader("Accept-Encoding").contains("gzip")) {
GZIPResponseWrapper wrapped = new GZIPResponseWrapper(s);
chain.doFilter(req, wrapped);
wrapped.finish(); // 確保壓縮資料寫出
} else {
chain.doFilter(req, resp);
}
}
}
public void init(FilterConfig config) {
// 單一參數
String encoding = config.getInitParameter("encoding");
// 所有參數
Enumeration names = config.getInitParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
System.out.println(name + " = " + config.getInitParameter(name));
}
// 取得 ServletContext
ServletContext ctx = config.getServletContext();
}
@WebFilter(asyncSupported = true) 並正確處理 AsyncContext。request.setAttribute()。