2.1 从Tomcat连接器到过滤器链


2.1 从 Tomcat 连接器到过滤器链

本节摘要:讲清请求踏入应用的头两站——连接器如何把字节流变成请求对象并分配线程,过滤器链如何完成进门检查。重点是比较过滤器与后续拦截器的职责差异,并用实验验证过滤器的执行顺序与"链"的含义。

第一站发生了什么

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 是关键:调用它,请求流向下一环;不调用,旅程在这里终止——安全框架拒绝未认证请求时用的正是"不交棒"这一手。

图 2-1 过滤器链的环环相扣

图 2-1 过滤器链的环环相扣

过滤器还是拦截器

两者都能在方法前后插一手,选错的代价是代码别扭、顺序失控。判断标准看两点:要不要在"业务方法"这一粒度上区分对待;是否需要在请求对象被 Spring 处理之前就介入。

维度 过滤器 拦截器
规范归属 Servlet 规范 Spring MVC 自有
生效范围 所有请求含静态资源 只覆盖进入分发器的请求
能否拿到处理器信息 不能 能拿到目标方法与注解
典型用途 编码、跨域、压缩、粗粒度安全 登录态校验、接口日志、权限细分

⚠️ 顺序陷阱:@Order 数值小的先执行,响应阶段顺序倒过来。把计时过滤器标成大数值,量到的耗时就不含前面各环——排查"耗时去哪了"时先确认自己的观察点在链上的位置。

实验:亲眼看链条的顺序与短路

背景:团队里常有人问"我的过滤器为什么没执行",答案几乎都在链的顺序与交棒上。操作三步。第一步,写两个过滤器,一个 @Order(1) 打"过滤器一进入/离开",一个 @Order(2) 打"过滤器二进入/离开",各在交棒调用前后打印。第二步,正常发一次请求,控制台输出为:过滤器一进入 → 过滤器二进入 → 过滤器二离开 → 过滤器一离开。结果解读:请求阶段按数值升序,响应阶段倒序,两层嵌套像洋葱——这正是"链"的含义。第三步做变式:把过滤器二的交棒调用注释掉,再发请求。输出只剩"过滤器一进入 → 过滤器二进入 → 过滤器一离开",控制器日志完全消失,浏览器拿到的是过滤器二里写死的响应。

这个短路实验的价值在于它演示了安全框架拒绝请求的原理:认证过滤器发现无凭证时,正是在这一环不交棒、直接写回 401。第 6 章配置安全规则时遇到"请求根本没到控制器",排查思路就回到本节——先画链、再定位哪一环没收棒。

另一个值得动手的对照:给过滤器加上"只对特定路径生效"的 URL 模式注册,验证静态资源与接口是否都被覆盖。经验结论:做全局计时放在链最外层,做跨域放在业务过滤器之前,做认证尽量交给专门的安全框架而非手写过滤器。

再补三个高频疑问。其一,过滤器能改请求参数吗?直接改不行,但可以用包装器把请求对象整个包一层,后续环节读到的就是包装后的版本——字符编码过滤器内部正是这么做的。其二,过滤器的执行顺序除了注解还有别的控制方式吗?注册器风格(配置类里逐个注册)按调用顺序排,两种方式混用时注册器的顺序规则优先级更高,团队里统一用一种最省心。其三,怎么确认某个请求到底过了哪些过滤器?开容器的过滤器调试日志,或者干脆在每个过滤器里统一打点——入口层的可见性是一切排错的地基。

本节要点回顾

  • 容器线程是稀缺资源:同步模式下请求全程占用,默认池约两百根
  • Spring MVC 装在一个 Servlet 后面:容器只见 Servlet 与过滤器
  • 交棒调用是链的心跳:不交棒即终止旅程
  • 过滤器粗筛、拦截器细筛:按粒度与时机选,不混用

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U