Servlet Filter 是 Web 容器請求處理管道的核心擴展點。本文從 Filter 接口三大方法、鏈路調用順序(請求前→目標→響應後)、web.xml 映射規則、初始化參數讀取、ServletContext.log() 日誌記錄五個維度,配合兩個完整 Filter 示例(SimpleFilter1、SimpleFilter2)與控制台輸出,徹底講透 Filter 工作機制。
public interface Filter {
void init(FilterConfig config) throws ServletException; // 容器啟動時調用一次
void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException; // 每請求調用
void destroy(); // 容器關閉時調用一次
}
配置兩個 Filter 映射 /*,順序為 filter1 → filter2:
<filter><filter-name>filter1</filter-name><filter-class>...SimpleFilter1</filter-class></filter>
<filter-mapping><filter-name>filter1</filter-name><url-pattern>/*</url-pattern></filter-mapping>
<filter><filter-name>filter2</filter-name><filter-class>...SimpleFilter2</filter-class></filter>
<filter-mapping><filter-name>filter2</filter-name><url-pattern>/*</url-pattern></filter-mapping>
控制台輸出:
Within SimpleFilter1:Filtering the Request...
Within SimpleFilter2:Filtering the Request...
Within SimpleFilter2:Filtering the Response...
Within SimpleFilter1:Filtering the Response...
結論:請求按配置順序正向傳遞(Filter1 → Filter2 → Servlet),響應按反向傳回(Servlet → Filter2 → Filter1)。chain.doFilter() 是分水嶺。
public class SimpleFilter1 implements Filter {
private FilterConfig filterConfig;
public void init(FilterConfig config) { this.filterConfig = config; }
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
try {
System.out.println("Within SimpleFilter1:Filtering the Request...");
chain.doFilter(req, res); // 必須調用,否則鏈路中斷
System.out.println("Within SimpleFilter1:Filtering the Response...");
} catch (IOException | ServletException e) { e.printStackTrace(); }
}
public void destroy() { this.filterConfig = null; }
}
在 doFilter 中將 ServletRequest 強轉為 HttpServletRequest,獲取 getRemoteHost()、getRequestURL()、當前時間,輸出至標準流或日誌。
web.xml 參數:
<init-param>
<param-name>startTime</param-name><param-value>22</param-value>
</init-param>
<init-param>
<param-name>endTime</param-name><param-value>6</param-value>
</init-param>
核心邏輯:
public void init(FilterConfig config) {
this.config = config;
context = config.getServletContext();
formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
startTime = Integer.parseInt(config.getInitParameter("startTime"));
endTime = Integer.parseInt(config.getInitParameter("endTime"));
}
public void doFilter(...) {
int currentHour = new GregorianCalendar().get(Calendar.HOUR_OF_DAY);
if (isUnusualTime(currentHour, startTime, endTime)) {
context.log("WARNING: " + req.getRemoteHost() + " accessed "
+ req.getRequestURL() + " on " + formatter.format(new Date()));
}
chain.doFilter(req, res);
}
// 跨午夜判斷邏輯
private boolean isUnusualTime(int current, int start, int end) {
if (start < end) return current >= start && current < end; // 同一天
else return !(current >= end && current < start); // 跨午夜
}
Tomcat 日誌配置(server.xml):
<Logger className="org.apache.catalina.logger.FileLogger"
prefix="catalina_log." suffix=".txt" timestamp="true"/>
日誌文件生成於 <CATALINA_HOME>/logs/localhost_log.YYYY-MM-DD.txt,按天滾動。
FilterChain.doFilter() 必須調用,否則請求不會到達目標 Servlet/JSP。url-pattern 時,web.xml 中聲明順序決定執行順序。FilterConfig.getServletContext() 可訪問全局上下文,適合記錄審計日誌。NumberFormatException 容錯,提供默認值。