如果你在搜索引擎里敲下“Servlet Filter”,大概率会同时搜出 JS 的 Array.filter、图像处理里的卷积滤波、模拟器音频面板的 filter level,甚至 Windows 事件过滤的报错。这些虽然都叫 filter,但和本文要聊的完全是两码事。我要写的是 Java Web 世界里那个能在 Servlet 动手之前先截胡的 Filter,它是 Servlet 规范的一部分,Tomcat、Jetty、Undertow 都认它。
很多人一开始会想:我直接在 Servlet 里写 if 判断不也一样吗?这个问题等你写到十几个 Servlet 都要做同一件事的时候,答案就出来了——如果没有 Filter,那段公共逻辑你会复制粘贴到手软,漏掉一个 Servlet 就是线上事故。Filter 就是把这些“几乎每个请求都要处理”的公共事(编码、登录校验、日志、跨域)从业务 Servlet 里剥离出来,交给容器在 Servlet 执行前后统一调度。
这篇文章适合刚接触 Java Web 被各种教程绕晕的新手,也适合工作了两三年但一直没系统梳理过 Filter 的开发者。我会从一次请求的流转讲起,给出完整可运行的 web.xml 版本示例,对比注解和 Spring Boot 注册方式,最后把我在真实项目里踩过的坑全部倒出来。你可以把它当成一篇“Filter 从入门到避坑”的参考,也欢迎看到最后和我讨论你的项目里是怎么组织 Filter 链的。
1. 一次HTTP请求进到Tomcat后,Filter站在哪里?
1.1 从Servlet规范的角度看Filter的定位
很多教程讲 Servlet 生命周期,都会说:浏览器发请求,Tomcat 匹配到对应的 Servlet,然后调 service()。这个模型其实少了 Filter 这一环,所以你可能会纳闷:为什么别人项目里所有请求进来,日志里先打印的是过滤器里的内容,而不是我的 Servlet。
Servlet 规范把 Filter 定义为一种可以“插”在请求和 Servlet 之间的组件。容器收到请求之后,不会直接扔给 Servlet,而是先走一遍已经注册的 Filter 链,等所有 Filter 决定放行之后,请求才真正进入 Servlet。你可以把它想成机场安检:从入口到登机口,中间要过证件核验、行李检查,全过了才能登机,Servlet 就是那个登机闸门。
Filter 能干三件事:第一,在请求到达 Servlet 之前做检查、拦截、加工;第二,调用 FilterChain 的 doFilter 方法放行,或者干脆不放行直接返回响应;第三,在 Servlet 处理完之后,对响应做后处理,比如统一加响应头、压缩输出内容。
这里有一个初学者常忽略的点:Filter 不依赖 Spring。哪怕你的项目还是最原始的 JSP+Servlet,只要跑在 Servlet 容器里,Filter 就能正常工作。它是 Servlet 规范自带的能力,不是 Spring MVC 的附属品。
1.2 FilterChain:一条由容器管理的责任链
Filter 通常不止一个,多个 Filter 会按照注册顺序串成一条链。请求从链头进来,经过每一个 Filter 的 doFilter,只要某个 Filter 不放行,链条就断了。
文字版流转顺序是这样的:
客户端请求 → Filter1 前置 → Filter2 前置 → Filter3 前置 → Servlet 业务 → Filter3 后置 → Filter2 后置 → Filter1 后置 → 返回给客户端
这里最关键的细节是:chain.doFilter(request, response) 这行代码之前的部分属于“请求前逻辑”,之后的部分属于“响应后逻辑”。理解了这一点,你就能读懂为什么有的 Filter 在进入 Servlet 前要改登录态,为什么有的 Filter 在 Servlet 处理完还要给响应头补一个安全标识。
我建议你亲手跑一遍验证:写两个简单 Filter,每个都在 doFilter 前后各打一条日志,Servlet 里再打一条,体会一下实际输出顺序。这个顺序会直接影响你排查线上问题时的思路,比如全链路日志的打印顺序、耗时统计的起止点,都和它挂钩。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用web.xml注册Filter:最规矩也最稳妥的写法
2.1 一个完整的Filter代码骨架
先给一个最通用的 Filter 代码结构。不管之后用注解还是注册器,核心方法永远是这三个:init()、doFilter()、destroy()。
init(FilterConfig config) 在容器启动时执行一次,用来读取初始化参数,比如编码名称、需要放行的 URL 列表。doFilter() 是每个请求都会进来的方法,干实际的活。destroy() 在容器销毁 Filter 实例时调用,一般用来释放资源。
我写一个登录校验 Filter,这是最常见的需求,而且能直接在 web.xml 项目里跑。
java复制package com.example.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 可以在这里读取 filterConfig.getInitParameter("xxx")
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServle
