Servlet Filter 作為請求/響應處理管道的核心組件,能在不修改 Servlet/JSP 的前提下實現橫切關注點。本文通過三個生產級案例——IP/域名黑名單阻斷、響應內容動態替換、GZIP 壓縮傳輸,完整展示 Filter 開發模式、HttpServletResponseWrapper 緩存響應技巧及 web.xml 配置要點。
doFilter(req, res, chain):核心邏輯,chain.doFilter() 前處理請求,後處理響應。init(FilterConfig):讀取初始化參數(如黑名單列表)。destroy():釋放資源。若需中斷鏈路:直接 res.sendRedirect() 或自行輸出響應,不調用 chain.doFilter()。
場景:根據遠程主機或 Referer 域名攔截惡意訪問。
public class BannedAccessFilter implements Filter {
private Set<String> bannedSites = new HashSet<>();
public void init(FilterConfig config) {
String param = config.getInitParameter("bannedSites");
new StringTokenizer(param).forEachRemaining(t -> bannedSites.add(t));
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
String remote = request.getRemoteHost();
String referer = getReferringHost(request.getHeader("Referer"));
if (bannedSites.contains(remote) || bannedSites.contains(referer)) {
showWarning((HttpServletResponse) res, bannedSites.contains(remote) ? remote : referer);
} else {
chain.doFilter(req, res);
}
}
// getReferringHost() 解析 Referer 域名
// showWarning() 輸出 HTML 禁止頁面
}
web.xml:<param-value>www.evil.com 127.0.0.1</param-value>,<url-pattern>/*</url-pattern> 全局生效。

難點:chain.doFilter() 執行後響應已提交,無法直接修改。
解法:自定義 CharArrayWrapper extends HttpServletResponseWrapper,重寫 getWriter() 返回寫入 CharArrayWriter 的 PrintWriter,攔截所有輸出。doFilter 中:
response → wrapperchain.doFilter(req, wrapper)String content = wrapper.toString() 獲取完整輸出content.replace(target, replacement)Content-Length,寫入原 response.getWriter()public abstract class ReplaceFilter implements Filter {
public void doFilter(...) {
CharArrayWrapper wrapper = new CharArrayWrapper((HttpServletResponse) response);
chain.doFilter(request, wrapper);
String out = wrapper.toString();
out = FilterUtils.replace(out, getTargetString(), getReplacementString());
response.setContentLength(out.length());
response.getWriter().write(out);
}
protected abstract String getTargetString();
protected abstract String getReplacementString();
}
// 具體實現
public class ReplaceSiteNameFilter extends ReplaceFilter {
public String getTargetString() { return "google.com.cn"; }
public String getReplacementString() { return "baidu.com"; }
}
web.xml:<url-pattern>/login.jsp</url-pattern> 精確匹配。

原理:瀏覽器發送 Accept-Encoding: gzip → 服務端壓縮響應體 → 設置 Content-Encoding: gzip → 瀏覽器自動解壓。
public class CompressionFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!isGzipSupported(request)) {
chain.doFilter(req, res); // 不支持直通
return;
}
response.setHeader("Content-Encoding", "gzip");
CharArrayWrapper wrapper = new CharArrayWrapper(response);
chain.doFilter(request, wrapper); // 收集輸出
char[] chars = wrapper.toCharArray();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(bos);
OutputStreamWriter writer = new OutputStreamWriter(gzip)) {
writer.write(chars);
}
response.setContentLength(bos.size());
response.getOutputStream().write(bos.toByteArray());
}
private boolean isGzipSupported(HttpServletRequest req) {
String enc = req.getHeader("Accept-Encoding");
return enc != null && enc.contains("gzip");
}
}
測試 Servlet:LongServlet 輸出 1 萬行重複文本,GZIP 壓縮比超 300:1。
<filter-mapping>
<filter-name>CompressionFilter</filter-name>
<servlet-name>LongServlet</servlet-name>
</filter-mapping>
CharArrayWrapper 適用於文本響應;二進制流(圖片/下載)需用 ByteArrayOutputStream 包裝 ServletOutputStream。ServletOutputStream 過濾鏈)。