Servlet 2.3 規範引入 Filter 機制,Tomcat 4 率先實現。不同於傳統 Web 伺服器的「掛鉤函數」模式,Servlet Filter 採用嵌套調用鏈架構:
doFilter(request, response, chain)chain.doFilter())、攔截並自行響應、修改請求/響應物件
public interface Filter {
void init(FilterConfig config); // 容器啟動時調用一次
void doFilter(ServletRequest req, ServletResponse res, FilterChain chain); // 每請求調用
void destroy(); // 容器關閉時調用
}
<web-app>
<!-- 1. 過濾器定義 -->
<filter>
<filter-name>IE Filter</filter-name>
<filter-class>com.ibm.devworks.filters.IEFilter</filter-class>
<init-param> <!-- 可選初始化參數 -->
<param-name>search</param-name>
<param-value>cannot</param-value>
</init-param>
</filter>
<!-- 2. 過濾器映射:決定攔截哪些 URL -->
<filter-mapping>
<filter-name>IE Filter</filter-name>
<url-pattern>/*</url-pattern> <!-- 攔截所有請求 -->
</filter-mapping>
</web-app>
關鍵規則:<filter-mapping> 在 web.xml 中的出現順序決定鏈執行順序,這直接影響業務邏輯結果。
public final class IEFilter implements Filter {
private FilterConfig filterConfig;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String ua = ((HttpServletRequest) request).getHeader("User-Agent").toLowerCase();
if (ua.contains("msie")) { // 檢測到 IE
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Sorry, page cannot be displayed!
");
out.flush();
return; // 關鍵:不調用 chain.doFilter(),直接終止後續處理
}
chain.doFilter(request, response); // 非 IE 正常放行
}
public void init(FilterConfig config) { this.filterConfig = config; }
public void destroy() {}
}
測試結果:IE 訪問顯示自定義拒絕頁;Netscape/Firefox 正常顯示原頁面。

核心難點:如何攔截並修改 Servlet/JSP 的輸出流? 答案是包裝響應物件。
class ReplaceTextStream extends ServletOutputStream {
private OutputStream realStream; // 真實響應流
private ByteArrayOutputStream buffer; // 緩衝所有寫入
private String searchText, replaceText;
public ReplaceTextStream(OutputStream out, String search, String replace) {
this.realStream = out;
this.buffer = new ByteArrayOutputStream();
this.searchText = search;
this.replaceText = replace;
}
public void write(int b) { buffer.write(b); } // 所有寫入導向緩衝區
// 關閉/刷新時執行實際替換並寫入真實流
public void close() throws IOException {
if (!closed) { processStream(); realStream.close(); closed = true; }
}
public void flush() throws IOException {
if (buffer.size() > 0 && !closed) { processStream(); buffer.reset(); }
}
private void processStream() throws IOException {
byte[] transformed = replaceContent(buffer.toByteArray());
realStream.write(transformed);
realStream.flush();
}
private byte[] replaceContent(byte[] input) {
String content = new String(input);
int idx = content.toLowerCase().indexOf(searchText.toLowerCase());
if (idx == -1) return input;
String result = content.substring(0, idx) + replaceText +
content.substring(idx + searchText.length());
return result.getBytes();
}
}
class ReplaceTextWrapper extends HttpServletResponseWrapper {
private ReplaceTextStream customStream;
private PrintWriter customWriter;
public ReplaceTextWrapper(ServletResponse response, String search, String replace)
throws IOException {
super((HttpServletResponse) response);
customStream = new ReplaceTextStream(response.getOutputStream(), search, replace);
customWriter = new PrintWriter(customStream);
}
@Override public ServletOutputStream getOutputStream() { return customStream; }
@Override public PrintWriter getWriter() { return customWriter; }
}
public final class ReplaceTextFilter implements Filter {
private String searchText = ".", replaceText = ".";
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
ReplaceTextWrapper wrapped = new ReplaceTextWrapper(res, searchText, replaceText);
chain.doFilter(req, wrapped); // 後續組件寫入的是包裝流
wrapped.getOutputStream().close(); // 強制觸發替換邏輯
}
public void init(FilterConfig config) {
String s = config.getInitParameter("search");
if (s != null) searchText = s;
s = config.getInitParameter("replace");
if (s != null) replaceText = s;
}
public void destroy() {}
}
<filter>
<filter-name>Replace Text Filter</filter-name>
<filter-class>com.ibm.devworks.filters.ReplaceTextFilter</filter-class>
<init-param><param-name>search</param-name><param-value>cannot</param-value></init-param>
<init-param><param-name>replace</param-name><param-value>must not</param-value></init-param>
</filter>
<filter-mapping>
<filter-name>Replace Text Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
效果:所有頁面輸出中的 "cannot" 自動變為 "must not",包含 JSP 動態生成內容。
同一 url-pattern 下,<filter-mapping> 先後順序決定執行順序:
實戰原則:web.xml 中映射順序即業務邏輯順序,設計時必須明確規劃。
掌握 Filter 包裝模式(HttpServletResponseWrapper + 自定義 Stream),即掌握了「響應後處理」的通用鑰匙——無論是 GZIP 壓縮、XSLT 轉換、還是敏感詞過濾,本質皆是如此。