Filter (過濾器) 是 Java Servlet 規範定義的核心組件,能在請求到達 Servlet 前、響應返回客戶端前進行**攔截、預處理、後處理**。它基於**責任鏈模式**,構成 Filter Chain,是實現横切關注點 (Cross-cutting Concerns) 的標準化方式。本文深度解析 Filter 生命週期、配置方式、核心 API,並詳解 5 大高頻實戰場景完整代碼。
@WebFilter(urlPatterns = "/*")
@Order(1)
public class LoggingFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
long start = System.currentTimeMillis();
try {
chain.doFilter(request, resp);
} finally {
long cost = System.currentTimeMillis() - start;
log.info("{} {} {}ms UA:{}", request.getMethod(), request.getRequestURI(), cost, request.getHeader("User-Agent"));
}
}
}
@WebFilter(urlPatterns = "/*")
@Order(2)
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
private boolean forceEncoding = true;
@Override
public void init(FilterConfig config) {
encoding = config.getInitParameter("encoding");
forceEncoding = Boolean.parseBoolean(config.getInitParameter("forceEncoding"));
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
if (forceEncoding || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (forceEncoding || response.getCharacterEncoding() == null) {
response.setCharacterEncoding(encoding);
}
chain.doFilter(request, response);
}
}
@WebFilter(urlPatterns = {"/api/*", "/admin/*"})
@Order(10)
public class AuthFilter implements Filter {
private static final Set WHITE_LIST = Set.of("/api/login", "/api/register", "/error");
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI();
if (WHITE_LIST.stream().anyMatch(uri::startsWith)) {
chain.doFilter(request, response);
return;
}
// JWT Token 驗證示例
String token = request.getHeader("Authorization");
if (token == null || !JwtUtil.validate(token.replace("Bearer ", ""))) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"未登入或Token無效\"}");
return;
}
// 可選:將用戶信息放入 Request Attribute 供後續使用
request.setAttribute("currentUser", JwtUtil.getUser(token));
chain.doFilter(request, response);
}
}
@WebFilter(urlPatterns = "/*")
@Order(20)
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
// 包裝 Request,覆寫 getParameter/Map/Values 進行清洗
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) req), resp);
}
static class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); }
private String clean(String value) {
if (value == null) return null;
return value.replaceAll("<", "<").replaceAll(">", ">")
.replaceAll("\\(", "(").replaceAll("\\)", ")")
.replaceAll("'", "'").replaceAll("\"", """);
}
@Override public String getParameter(String name) { return clean(super.getParameter(name)); }
@Override public String[] getParameterValues(String name) {
String[] vals = super.getParameterValues(name);
if (vals == null) return null;
return Arrays.stream(vals).map(this::clean).toArray(String[]::new);
}
@Override public Map getParameterMap() {
Map map = super.getParameterMap();
Map cleanMap = new HashMap<>();
map.forEach((k, v) -> cleanMap.put(k, Arrays.stream(v).map(this::clean).toArray(String[]::new)));
return cleanMap;
}
}
}
@WebFilter(urlPatterns = "/*")
@Order(30)
public class GzipFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 檢查 Client 支援 gzip
String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
GzipHttpServletResponseWrapper wrapper = new GzipHttpServletResponseWrapper(response);
try {
chain.doFilter(request, wrapper);
} finally {
wrapper.finish(); // 刷新 GZIPOutputStream,寫入 Footer
}
} else {
chain.doFilter(request, response);
}
}
static class GzipHttpServletResponseWrapper extends HttpServletResponseWrapper {
private GZIPOutputStream gzipOS;
private ServletOutputStream sos;
private PrintWriter pw;
public GzipHttpServletResponseWrapper(HttpServletResponse response) { super(response); }
@Override public ServletOutputStream getOutputStream() throws IOException {
if (pw != null) throw new IllegalStateException("getWriter() already called");
if (sos == null) {
sos = getResponse().getOutputStream();
gzipOS = new GZIPOutputStream(sos);
}
return new ServletOutputStream() {
@Override public void write(int b) throws IOException { gzipOS.write(b); }
@Override public void write(byte[] b, int off, int len) throws IOException { gzipOS.write(b, off, len); }
@Override public void flush() throws IOException { gzipOS.flush(); }
@Override public void close() throws IOException { gzipOS.finish(); }
@Override public boolean isReady() { return sos.isReady(); }
@Override public void setWriteListener(WriteListener l) { sos.setWriteListener(l); }
};
}
@Override public PrintWriter getWriter() throws IOException {
if (sos != null) throw new IllegalStateException("getOutputStream() already called");
if (pw == null) {
pw = new PrintWriter(new OutputStreamWriter(getOutputStream(), getResponse().getCharacterEncoding()));
}
return pw;
}
public void finish() throws IOException {
if (gzipOS != null) gzipOS.finish();
if (pw != null) pw.flush();
}
@Override public void setContentLength(int len) { /* 忽略,壓縮後長度未知 */ }
@Override public void setContentLengthLong(long len) { /* 忽略 */ }
}
}
| 維度 | Filter (Servlet) | Interceptor (Spring MVC) | AOP (Spring) |
|---|---|---|---|
| 作用範圍 | 所有請求 (含靜態資源、JSP、Servlet) | 僅 Spring MVC Controller 請求 | Spring Bean 方法 (Service、Repository 等) |
| 依賴容器 | Servlet 容器 (Tomcat/Jetty) | Spring MVC DispatcherServlet | Spring 容器 |
| 獲取 Spring Bean | 需手動從 WebApplicationContext 獲取 | 直接注入 | 直接注入 |
| 適用場景 | 編碼、日誌、壓縮、安全攔截、XSS、靜態資源 | Controller 級權限、參數驗證、日誌、事務 (配合) | 業務邏輯切面、事務、緩存、鎖、監控、異常統一處理 |
Filter 是 Java Web 基礎設施的基石,掌握其生命週期、鏈路傳遞、包裝模式與排序控制,配合 5 大經典場景模板,能解決 80% 以上的横切關注點需求。實務中建議:通用基礎設施 (編碼、日誌、壓縮、安全) 用 Filter;Controller 級邏輯用 Interceptor;業務級切面用 AOP,分層清晰、職責單一、易於維護擴展。