本节摘要:讲清请求踏入应用的头两站——连接器如何把字节流变成请求对象并分配线程,过滤器链如何完成进门检查。重点是比较过滤器与后续拦截器的职责差异,并用实验验证过滤器的执行顺序与"链"的含义。
Spring Boot 内嵌的 Tomcat 在启动时创建了两类线程:接收线程守在端口上完成 TCP 握手与 HTTP 解析,然后把请求交给工作线程池。你的每个请求从工作线程的视角看是"一次性任务":解析请求行与头部、构造请求响应对象、沿过滤器链传递。理解这一点的实用价值在于线程模型——同步模式下这根线程从进入到响应写出全程被占用,线程池默认两百根,这就是为什么慢数据库能拖垮整个应用:占着线程不还。
容器本身与 Spring 无关,它遵守的是 Servlet 规范。Spring 做的事是把一个 DispatcherServlet 注册进容器,并让所有请求路径都指向它。可以用代码看到这层注册:
public class BootWebInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) { ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet()); dispatcher.setLoadOnStartup(1); dispatcher.addMapping("/"); } }
在 Spring Boot 里这段由自动配置代劳,但机制不变:容器认识的只有 Servlet 和过滤器,Spring MVC 的全部魔法都装在这一个 Servlet 后面。
过滤器在 Servlet 之前按声明顺序执行,职责是那些"与具体业务无关的进门事务":字符编码、跨域预检、请求包装、压缩、计时。写一个带顺序的过滤器:
@Component @Order(1) public class CharsetFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); chain.doFilter(req, resp); // 交棒给下一环 } }
chain.doFilter 是关键:调用它,请求流向下一环;不调用,旅程在这里终止——安全框架拒绝未认证请求时用的正是"不交棒"这一手。

两者都能在方法前后插一手,选错的代价是代码别扭、顺序失控。判断标准看两点:要不要在"业务方法"这一粒度上区分对待;是否需要在请求对象被 Spring 处理之前就介入。
| 维度 | 过滤器 | 拦截器 |
|---|---|---|
| 规范归属 | Servlet 规范 | Spring MVC 自有 |
| 生效范围 | 所有请求含静态资源 | 只覆盖进入分发器的请求 |
| 能否拿到处理器信息 | 不能 | 能拿到目标方法与注解 |
| 典型用途 | 编码、跨域、压缩、粗粒度安全 | 登录态校验、接口日志、权限细分 |
⚠️ 顺序陷阱:
@Order数值小的先执行,响应阶段顺序倒过来。把计时过滤器标成大数值,量到的耗时就不含前面各环——排查"耗时去哪了"时先确认自己的观察点在链上的位置。
背景:团队里常有人问"我的过滤器为什么没执行",答案几乎都在链的顺序与交棒上。操作三步。第一步,写两个过滤器,一个 @Order(1) 打"过滤器一进入/离开",一个 @Order(2) 打"过滤器二进入/离开",各在交棒调用前后打印。第二步,正常发一次请求,控制台输出为:过滤器一进入 → 过滤器二进入 → 过滤器二离开 → 过滤器一离开。结果解读:请求阶段按数值升序,响应阶段倒序,两层嵌套像洋葱——这正是"链"的含义。第三步做变式:把过滤器二的交棒调用注释掉,再发请求。输出只剩"过滤器一进入 → 过滤器二进入 → 过滤器一离开",控制器日志完全消失,浏览器拿到的是过滤器二里写死的响应。
这个短路实验的价值在于它演示了安全框架拒绝请求的原理:认证过滤器发现无凭证时,正是在这一环不交棒、直接写回 401。第 6 章配置安全规则时遇到"请求根本没到控制器",排查思路就回到本节——先画链、再定位哪一环没收棒。
另一个值得动手的对照:给过滤器加上"只对特定路径生效"的 URL 模式注册,验证静态资源与接口是否都被覆盖。经验结论:做全局计时放在链最外层,做跨域放在业务过滤器之前,做认证尽量交给专门的安全框架而非手写过滤器。
再补三个高频疑问。其一,过滤器能改请求参数吗?直接改不行,但可以用包装器把请求对象整个包一层,后续环节读到的就是包装后的版本——字符编码过滤器内部正是这么做的。其二,过滤器的执行顺序除了注解还有别的控制方式吗?注册器风格(配置类里逐个注册)按调用顺序排,两种方式混用时注册器的顺序规则优先级更高,团队里统一用一种最省心。其三,怎么确认某个请求到底过了哪些过滤器?开容器的过滤器调试日志,或者干脆在每个过滤器里统一打点——入口层的可见性是一切排错的地基。