Appearance
Spring 中的过滤器、过滤器链、拦截器与 DispatcherServlet
很多人第一次学 Spring Web 主线时,很容易把下面这些词混在一起:
Filter- 过滤器链
InterceptorDispatcherServlet
它们都和“请求怎么进来、怎么被处理”有关,但职责并不一样。
这条主线真正要回答的是:一个 HTTP 请求进入 Spring 应用后,先经过谁,再交给谁,哪些逻辑适合放在请求入口层,哪些逻辑适合放在 MVC 处理层。
这篇文章重点讲:
Filter到底是什么- 过滤器链在控制什么
Interceptor和Filter有什么区别DispatcherServlet在整条链路中处于什么位置Spring Security为什么严重依赖过滤器链- 工程上应该怎么选、怎么落代码
1. 先建立一个整体认识
如果先不急着背接口名,可以把一条请求的主线理解成:
- 请求先进入 Servlet 容器
- 先经过一组
Filter - 再进入
DispatcherServlet DispatcherServlet再把请求分发给具体 Controller- Controller 执行前后,可能还会经过
Interceptor
看一张总流程图:
mermaid
flowchart TD
A[HTTP 请求进入应用] --> B[Servlet Filter 1]
B --> C[Servlet Filter 2]
C --> D[Spring Security 过滤器链]
D --> E[DispatcherServlet]
E --> F[HandlerInterceptor preHandle]
F --> G[Controller 方法]
G --> H[HandlerInterceptor postHandle]
H --> I[视图解析或响应写出]
I --> J[HandlerInterceptor afterCompletion]
J --> K[HTTP 响应返回]这张图最重要的意思是:
Filter更靠近 Servlet 容器入口Interceptor更靠近 Spring MVC 方法调用链DispatcherServlet是 Spring MVC 的统一调度中心
2. Filter 到底是什么
Filter 可以看成 Servlet 规范层面的请求过滤器。
它解决的是 请求在真正进入业务处理之前,是否要先统一做一层预处理、包装、校验或拦截。
它最常见的能力通常包括:
- 请求日志
- 字符编码处理
- token 提取
- 跨域处理
- 请求包装
- 黑名单 / 白名单拦截
这里要特别注意:
Filter 不是 Spring MVC 独有的概念。
它更靠近 Servlet 容器这一层,所以即使不用 Spring MVC,Servlet Web 应用里也会有 Filter。
2.1 一个最简单的 Filter 示例
java
@Component
public class TraceFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
long start = System.currentTimeMillis();
try {
// 放行给后续过滤器或目标处理逻辑
chain.doFilter(request, response);
} finally {
System.out.println(httpRequest.getRequestURI()
+ " cost=" + (System.currentTimeMillis() - start));
}
}
}这里真正关键的动作是:chain.doFilter(request, response)
它的意思不是“当前过滤器结束”,而是:把请求继续交给后面的过滤器链或最终目标处理逻辑。
如果不调用它,请求通常就不会继续往下走。
3. 过滤器链到底在说什么
单个 Filter 不难理解,真正容易混的是“过滤器链”。
过滤器链可以看成 多个 Filter 按顺序串起来,共同处理同一条请求。
它解决的是 请求入口层的通用逻辑,不要都塞进一个超大过滤器里,而要拆成多段、按顺序协作。
例如一条请求进来后,可能依次经过:
- 编码过滤器
- 请求日志过滤器
- 安全认证过滤器
- token 解析过滤器
- 异常包装过滤器
这时“谁先跑、谁后跑”就变得非常重要。
因为:前一个过滤器的处理结果,往往会直接影响后一个过滤器看到的请求状态。
3.1 为什么顺序很重要
例如:
- 如果 token 解析还没做,后面的权限过滤器可能拿不到用户身份
- 如果跨域响应头还没写,浏览器可能直接拦掉响应
- 如果日志过滤器放在过后位置,可能拿不到完整上下文
所以过滤器链真正要管的,不只是“有哪些过滤器”,而是:它们按什么顺序协作。
4. DispatcherServlet 在哪里
很多人学 Spring MVC 时,看到 DispatcherServlet 就以为:
所有请求一进应用,第一站就是 DispatcherServlet。
这个理解不够完整。DispatcherServlet 是 Spring MVC 的统一入口,但在它之前,请求通常已经先经过 Servlet Filter 链。可以直接这样理解分工:
Filter:更靠近容器入口,处理更通用的 Web 请求问题DispatcherServlet:进入 Spring MVC 后的调度中心Interceptor:进入 MVC 处理链后,对 Controller 调用过程做增强
所以:
Filter 在 DispatcherServlet 外面,Interceptor 在 DispatcherServlet 里面。
5. Interceptor 又是什么
Interceptor 可以看成 Spring MVC 提供的方法调用拦截器。
它解决的是 当请求已经进入 Spring MVC 体系后,是否需要在 Controller 执行前后插入一些逻辑。
它更常见的场景通常包括:
- 登录态校验
- 接口访问审计
- 权限补充判断
- 统一埋点
- 请求级上下文准备
5.1 一个最简单的 Interceptor 示例
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = request.getHeader("Authorization");
if (token == null || token.isBlank()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return true;
}
@Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("request finished: " + request.getRequestURI());
}
}然后在配置里注册:
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final LoginInterceptor loginInterceptor;
public WebMvcConfig(LoginInterceptor loginInterceptor) {
this.loginInterceptor = loginInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}这里要特别注意:
Interceptor 不会拦到所有 Servlet 资源,它主要工作在 Spring MVC 的 Handler 调用链上。
6. Filter 和 Interceptor 到底有什么区别
这是这条主线里最常见的问题。
看一张对比表:
| 对比维度 | Filter | Interceptor |
|---|---|---|
| 所属层级 | Servlet 规范层 | Spring MVC 层 |
| 执行位置 | DispatcherServlet 之前 | DispatcherServlet 之后、Controller 前后 |
| 主要对象 | ServletRequest / ServletResponse | HttpServletRequest、Handler、ModelAndView 等 |
| 能否拦截静态资源 | 通常可以 | 通常主要针对 MVC Handler |
| 更适合做什么 | 编码、跨域、请求包装、统一入口校验 | 登录态校验、权限补充、埋点、Controller 级增强 |
| 是否依赖 Spring MVC | 不一定 | 是 |
一句话压缩:
Filter更偏请求入口层Interceptor更偏 MVC 方法调用层
6.1 怎么选更稳妥
工程上通常可以这样判断:
- 如果逻辑更靠近“请求进入应用的第一层”,优先考虑
Filter - 如果逻辑明显依赖 Spring MVC 的 Handler、方法级语义,优先考虑
Interceptor
例如:
- 跨域、编码、请求包装,适合
Filter - Controller 调用前的登录态校验、接口级审计,适合
Interceptor
7. 为什么 Spring Security 严重依赖过滤器链
很多人第一次接触 Spring Security 时会觉得:怎么一堆认证、授权、上下文、异常处理都和过滤器有关。
原因并不复杂。
因为 Spring Security 最核心的设计思想就是:在请求进入业务层之前,先通过一整套安全过滤器链把认证、授权、异常响应这些问题统一处理掉。
这意味着:
- 请求一进来,先经过安全过滤器链
- 过滤器提取 token、Session、用户名密码等认证信息
- 构建
SecurityContext - 再决定是否允许继续访问后面的 Controller
也就是说:Spring Security 不是主要靠 Controller 注解撑起来的,而是主要靠过滤器链撑起来的。
7.1 SecurityFilterChain 到底是什么
SecurityFilterChain 可以看成 Spring Security 维护的一组安全过滤器规则集合。
例如:
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login").permitAll()
.anyRequest().authenticated());
return http.build();
}
}这里真正的重点不是某个方法名,而是:你在配置一条“哪些请求走什么安全规则”的过滤器链。
8. OncePerRequestFilter 为什么经常出现
在 Spring Security 或自定义认证场景里,经常会看到:OncePerRequestFilter
它可以看成 Spring 提供的一个更适合 Web 请求场景的过滤器基类,用来帮助你保证一次请求只执行一次核心过滤逻辑。
它特别适合:
- token 解析
- JWT 校验
- 请求上下文初始化
例如一个简化版 JWT 过滤器:
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
// 这里只做示意:真实项目里通常还要解析 token、查用户、构建权限信息
request.setAttribute("currentUser", "tom");
}
filterChain.doFilter(request, response);
}
}要注意的是:它仍然是 Filter,只是 Spring 帮你封装成了更适合请求场景的基类。
9. Spring 里常见的过滤器类有哪些
很多人学到这里时,会继续碰到另一个问题:真正写项目时,经常会看到一堆 Filter 类名,它们分别是干什么的?
这时最容易乱的点,不是记不住类名,而是:不知道这些类分别属于 Servlet 层、Spring Web 层,还是 Spring Security 层。
可以把常见过滤器类按 3 组理解:
- 通用过滤器接口与基类
- Spring Web 常见基础过滤器
- Spring Security 入口与安全过滤器链相关类
9.1 通用过滤器接口与基类
看最常见的一组:
| 类 / 接口 | 所属层次 | 主要作用 | 什么时候常见 |
|---|---|---|---|
Filter | Servlet 规范层 | 定义过滤器的基础接口 | 几乎所有 Web 过滤器的起点 |
GenericFilterBean | Spring Web 层 | 把 Filter 包装成更适合 Spring Bean 管理的基类 | 想写 Spring 风格过滤器,但不需要“一次请求只执行一次”语义时 |
OncePerRequestFilter | Spring Web 层 | 保证一次请求只执行一次核心过滤逻辑 | JWT、登录态、上下文初始化这类场景 |
这里最值得先记住的是:
Filter是最原始的 Servlet 接口GenericFilterBean是 Spring 为了更方便接入 Bean 生命周期做的基类封装OncePerRequestFilter则是在这个基础上,进一步强调“一次请求只做一次”
也就是说,它们不是彼此平级重复,而是:越往上越偏 Spring,越往下越偏 Servlet 原生接口。
9.2 GenericFilterBean 是什么
GenericFilterBean 可以看成 Spring 提供的一个过滤器基类,用来让 Filter 更自然地融入 Spring Bean 生命周期和配置体系。
它解决的是 如果你既想写 Filter,又希望它能更方便地使用 Spring 容器能力,那么直接继承这个基类会比裸写 Filter 更顺手。
例如:
java
@Component
public class TraceIdFilter extends GenericFilterBean {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
request.setAttribute("traceId", httpRequest.getRequestURI() + "-001");
chain.doFilter(request, response);
}
}不过工程上如果你的过滤逻辑明确是“每个请求只执行一次”,通常还是更优先考虑:OncePerRequestFilter
9.3 Spring Web 里常见的基础过滤器
除了自己写过滤器,Spring Web 本身也提供了一些非常常见的基础过滤器类。
| 过滤器类 | 主要作用 | 常见场景 |
|---|---|---|
CharacterEncodingFilter | 统一请求和响应编码 | 避免中文乱码 |
CorsFilter | 处理跨域请求 | 前后端分离接口跨域 |
FormContentFilter | 让表单数据在非 POST 场景下也能更方便参与参数绑定 | PUT、PATCH 等表单提交 |
HiddenHttpMethodFilter | 让表单通过隐藏字段模拟 PUT、DELETE 等方法 | 传统服务端页面表单 |
ForwardedHeaderFilter | 处理反向代理转发头 | Nginx、网关、负载均衡之后恢复原始协议和地址 |
ShallowEtagHeaderFilter | 生成浅层 ETag 响应头 | 简单缓存协商场景 |
这些类最容易讲混的一点是:它们大多不是在做认证授权,而是在做 Web 基础设施层的通用处理。
例如:
CharacterEncodingFilter解决编码一致性CorsFilter解决浏览器跨域ForwardedHeaderFilter解决代理之后请求头还原
也就是说,它们更像:请求进入业务之前的一层通用环境整理。
9.4 Spring Security 相关的过滤器类怎么理解
到了安全场景,常见类名又会变一组。
这时最先要分清的通常是:
| 类 | 主要作用 |
|---|---|
DelegatingFilterProxy | 把 Servlet 容器中的 Filter 调用桥接到 Spring 容器里的 Bean |
FilterChainProxy | Spring Security 内部真正管理多条安全过滤器链的核心代理 |
UsernamePasswordAuthenticationFilter | 处理表单登录认证 |
BasicAuthenticationFilter | 处理 HTTP Basic 认证 |
BearerTokenAuthenticationFilter | 处理 Bearer Token / OAuth2 资源服务器认证 |
ExceptionTranslationFilter | 统一把认证、授权异常转换成 HTTP 响应 |
这里最该注意的是:
DelegatingFilterProxy更像入口桥接器FilterChainProxy更像安全过滤器总调度中心- 后面那些认证、授权类过滤器,才是具体做安全处理的执行组件
如果你想把这组类再往下细看,可以接着看:
9.5 工程上该怎么选
如果把这些常见过滤器类放到实际项目里,可以这样判断:
- 如果你只是要实现最基础的 Servlet 过滤逻辑,可以直接实现
Filter - 如果你希望它更自然地接入 Spring Bean 管理,可以优先考虑
GenericFilterBean - 如果你做的是 JWT、登录态、请求上下文这类一次请求只应执行一次的逻辑,优先考虑
OncePerRequestFilter - 如果你处理的是编码、跨域、反向代理头、表单方法转换这些通用 Web 问题,优看 Spring 提供的基础过滤器
- 如果你处理的是认证、授权、安全异常,优先放到 Spring Security 过滤器链里,而不是自己随意拼过滤器顺序
10. Filter 声明后是怎么进入容器并装配进过滤器链的
很多人写完一个过滤器类之后,接着就会疑惑:这个类到底是怎么被容器发现的,又是怎么真正出现在请求过滤链里的?
这里最容易混的,其实是两个“容器”:
Spring 容器:负责管理 BeanServlet 容器:负责真正维护 Web 过滤器链
🌟 这两个不是一回事。
Filter 要真正参与 HTTP 请求处理,最终一定要被注册到 Servlet 容器维护的过滤器链里;但在 Spring 项目中,这个注册过程经常又会借助 Spring 容器完成。
10.1 先分清“被 Spring 管理”和“进入过滤器链”不是同一件事
一个过滤器类即使已经被 Spring 扫描成 Bean,也不等于它已经自动进了 Servlet 过滤器链。
真正要发生两件事:
- 过滤器对象要能被创建和管理
- 过滤器要被注册到 Web 容器,指定拦截路径、顺序和生效范围
对应到两个容器,可以直接这样分工:
Spring 容器解决“这个对象谁来创建、依赖怎么注入”Servlet 容器解决“这个过滤器要不要拦请求、拦哪些请求、排在第几位”
10.2 最常见的几种注册方式
工程上最常见的方式通常有 4 种。
10.2.1 直接实现 Filter,再交给 Spring Boot 自动注册
在 Spring Boot 项目里,最常见的一种体验是:
- 你写一个
Filter实现类 - 再把它交给 Spring 管理,例如加上
@Component - Spring Boot 在启动阶段把它注册到 Servlet 容器
例如:
java
@Component
public class TraceFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
}这种方式最方便,但默认也意味着:你把过滤器 Bean 交给了 Spring,再由 Spring Boot 帮你完成注册。
它的优点是简单,缺点是当你要精细控制顺序、URL 模式、是否启用时,通常不如显式注册灵活。
10.2.2 使用 FilterRegistrationBean 显式注册
如果你想更清楚地控制过滤器怎么进入链路,最常见的工程写法就是:FilterRegistrationBean
它可以理解成 Spring Boot 提供的一个注册器,用来把某个过滤器明确注册到 Servlet 容器,并指定顺序、URL 模式等信息。
例如:
java
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<TraceFilter> traceFilterRegistration(TraceFilter traceFilter) {
FilterRegistrationBean<TraceFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(traceFilter);
registration.addUrlPatterns("/api/*");
registration.setOrder(10);
registration.setName("traceFilter");
return registration;
}
}这里最重要的不是 API 名,而是它在表达:
- 用哪个过滤器实例
- 拦哪些路径
- 在链路里排第几位
- 是否启用某个名字或特定配置
所以如果你问“Filter 是怎么装配进链路的”,FilterRegistrationBean 是最典型、最直观的答案之一。
10.2.3 使用 @WebFilter,交给 Servlet 组件扫描
另一种方式更偏 Servlet 原生风格,也就是:@WebFilter
例如:
java
@WebFilter(urlPatterns = "/api/*", filterName = "traceFilter")
public class TraceFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
}如果走这条路,通常还需要:@ServletComponentScan
这样 Spring Boot 才会去扫描这些 Servlet 组件注解。
这种方式更接近传统 Servlet 编程风格,但在 Spring Boot 项目里,很多时候大家更常用的还是:
@ComponentFilterRegistrationBean
因为它们和 Spring Bean 管理体系结合得更自然。
10.2.4 Spring Security 里的注册方式更特殊
如果到了 Spring Security,事情又不一样了。
很多安全过滤器并不是你一个个手工塞进 Servlet 过滤器链的,而是:
- Servlet 容器先接入
DelegatingFilterProxy - 它把请求转交给 Spring 容器里的安全过滤器代理 Bean
- 再由
FilterChainProxy根据请求匹配某条SecurityFilterChain - 最后执行这条链里的多个安全过滤器
也就是说:Spring Security 更像是把一个总入口 Filter 挂进 Servlet 容器,再由这个总入口在 Spring 内部继续调度真正的安全过滤器链。
这也是为什么你在安全场景里经常不是直接问:这个 Filter 有没有注册进去
而是更常问:它有没有被加到 SecurityFilterChain 的正确位置
10.3 过滤器顺序是怎么决定的
过滤器能不能进链只是第一步,另一个真正影响行为的问题是:它排在谁前面,谁后面。
这很重要,因为:
- 编码过滤器通常要尽量靠前
- token 解析要在授权判断之前
- 异常包装过滤器要能包住后面的链路
常见的控制方式包括:
FilterRegistrationBean#setOrder(...)- Spring Bean 排序相关机制
- 在 Spring Security 里用
addFilterBefore、addFilterAfter、addFilterAt
所以真正完整的问题不是“Filter 有没有注册”,而是“Filter 是怎么注册的,又被放在链路的哪个位置”。
10.4 一句话把装配过程串起来
如果把这条链压缩成一句话,可以记成:
- 普通 Web 过滤器:
Spring 创建 Bean -> 注册到 Servlet 容器 -> 进入 Filter 链 - Spring Security 过滤器:
Servlet 容器接入 DelegatingFilterProxy -> Spring 内部交给 FilterChainProxy -> 匹配 SecurityFilterChain -> 执行具体安全过滤器
真正需要记住的核心不是具体某个注解,而是:Filter 最终必须被 Servlet 容器链路接纳,Spring 只是经常帮助你完成这个注册与装配过程。
11. 一条请求到底怎么走
如果把前面这些角色真正串起来,可以按这条顺序理解:
- 浏览器或客户端发起 HTTP 请求
- 请求先进入 Servlet 容器
- 先经过容器里的
Filter链 - 如果启用了 Spring Security,还会经过安全过滤器链
- 然后进入
DispatcherServlet DispatcherServlet找到目标 Controller- 在 Controller 调用前后,可能触发
Interceptor - Controller 返回结果
- 响应再一路返回给客户端
所以最值得记住的一句话是:Filter 更外层,Interceptor 更内层,DispatcherServlet 是 Spring MVC 的调度中心。
11.1 一个请求到响应在 Spring 中的完整链路
如果我们不只想记住几个组件名,而是真的想看懂:一条请求为什么会先过过滤器,再进 DispatcherServlet,最后又是怎么把响应写回去的。
那可以按下面这条主线来理解。
mermaid
flowchart TD
A[客户端发起 HTTP 请求] --> B[Tomcat 等 Servlet 容器接收请求]
B --> C[Servlet Filter 链]
C --> D[Spring Security 过滤器链]
D --> E[DispatcherServlet 接管请求]
E --> F[HandlerMapping 查找目标处理器]
F --> G[HandlerAdapter 调用前准备]
G --> H[Interceptor preHandle]
H --> I[参数绑定与类型转换]
I --> J[参数校验]
J --> K[执行 Controller 方法]
K --> L[调用 Service 与 DAO]
L --> M[返回处理结果]
M --> N[Interceptor postHandle]
N --> O[视图解析或消息转换写回]
O --> P[Interceptor afterCompletion]
P --> Q[Filter 链返回]
Q --> R[客户端收到 HTTP 响应]
J --> S[异常解析器处理异常]
K --> S
L --> S
S --> O这张图里最值得抓住的不是每个类名,而是三层分工:
Servlet 容器层:负责接收 HTTP 请求,并先让Filter链处理Spring MVC 调度层:由DispatcherServlet负责把请求分发给正确的 Handler业务执行层:真正调用 Controller、Service、DAO,最后再把结果转换成 HTTP 响应
11.2 把这条链路拆开看
11.2.1 请求进入 Servlet 容器
浏览器、前端或其他服务发出 HTTP 请求后,最先接住它的并不是 Controller,而是:
Tomcat、Jetty、Undertow 这类 Servlet 容器。
它们解决的是网络连接、请求接收、线程分配这些更底层的问题。
到了 Spring Boot 项目里,虽然我们平时主要写的是 Spring 代码,但真正接 HTTP 请求的底座,仍然是这层 Web 容器。
11.2.2 先过 Filter,再决定是否继续放行
请求进入容器后,通常会先进入 Filter 链。
这一步常做的事情包括:
- 字符编码处理
- 跨域响应头处理
- 请求日志记录
- token 初步提取
- 请求包装或统一上下文初始化
如果项目用了 Spring Security,那么安全过滤器链通常也会在这个阶段参与进来。
也就是说,很多认证、鉴权、匿名访问判断,并不是等到 Controller 里才做,而是在更前面的过滤器阶段就已经做掉了。
11.2.3 进入 DispatcherServlet
当前面的过滤器都放行后,请求才会真正进入 Spring MVC 的统一入口:DispatcherServlet
它可以理解成“总调度台”。
它自己通常不直接执行业务,而是负责:
- 找到这条请求该交给哪个 Controller 方法
- 选择合适的调用适配器
- 组织参数绑定、校验、返回值处理、异常处理
所以 DispatcherServlet 真正解决的是:把一个原始 HTTP 请求,组织成一次标准化的 Spring MVC 调用过程。
11.2.4 查找 Handler,并准备调用
DispatcherServlet 接到请求后,通常会先通过 HandlerMapping 查找目标 Handler。
例如:
- 请求路径是
/user/1 - HTTP 方法是
GET - 最终匹配到
UserController#getUser
找到目标之后,再由 HandlerAdapter 负责把“找到 Handler”这件事转换成“真正可以执行的方法调用”。
更直白一点说:
HandlerMapping负责找人HandlerAdapter负责真正把人叫起来干活
11.2.5 Interceptor 在 Controller 前后插入逻辑
在真正调用 Controller 之前,Spring MVC 还会先执行拦截器的 preHandle。
这一层更适合做:
- 登录态补充判断
- 接口级审计
- 请求链路标识注入
- 与具体 Handler 强相关的上下文准备
如果 preHandle 返回 false,请求链路通常会被提前中止,不再继续调用 Controller。
如果成功放行,才会继续走后面的参数绑定与业务执行。
11.2.6 参数绑定、类型转换与校验
当请求已经确定要调用某个 Controller 方法时,Spring MVC 会继续做几件非常关键的事:
- 从 URL、Query、Header、Body 中取值
- 把字符串或 JSON 转成 Java 对象
- 执行类型转换
- 执行
@Valid、@Validated等参数校验
例如前端传来 JSON,请求体里的字段会先经过消息转换器反序列化成 Java 对象,再执行校验注解。
如果这一步失败,很多场景下甚至不会进入你的 Controller 方法,而是直接交给异常处理流程。
11.2.7 调用 Controller、Service、DAO
前面的准备都完成后,才会真正执行 Controller 方法。
Controller 通常只负责 Web 层职责,例如:
- 接收参数
- 调用 Service
- 返回统一响应对象
然后 Service 再负责业务编排,DAO 或 Repository 再负责数据库访问。
所以从职责划分上看,这一段链路通常是:Controller -> Service -> DAO
这也是为什么我们总说:Controller 不应该承载太重的业务逻辑。
11.2.8 返回值如何变成 HTTP 响应
业务方法执行完后,返回结果并不会原样直接丢给浏览器。
Spring MVC 还要继续做响应处理。
常见情况包括:
- 如果返回的是视图名,就走视图解析
- 如果返回的是对象,而 Controller 使用了
@ResponseBody或@RestController,就会交给HttpMessageConverter转成 JSON - 如果有拦截器,还会执行
postHandle和afterCompletion
这里的 HttpMessageConverter 可以看成 负责在 Java 对象和 HTTP 消息体之间做转换的组件。
也正因为有它,Controller 才可以很自然地返回一个 Java 对象,而不是手动拼接 JSON 字符串。
11.2.9 如果中间出错,会怎么走
真实工程里,一条请求并不总是顺利走到返回。
常见的异常可能出现在:
- 参数绑定失败
- 参数校验失败
- Controller 抛异常
- Service 或 DAO 抛异常
这时 Spring MVC 通常会把异常交给异常解析器或全局异常处理机制,例如 @ControllerAdvice。
也就是说,异常处理本身也是请求链路的一部分,而不是“链路之外的补丁逻辑”。
11.2.10 响应写回之后,这条链才真正结束
当响应体被写回 HttpServletResponse 之后,请求才算接近尾声。
但这里还有两个容易被忽略的点:
Interceptor的afterCompletion往往会在这个阶段执行清理逻辑- 外层
Filter在chain.doFilter()返回后,也会继续执行自己的收尾代码
这就是为什么很多日志过滤器会把开始时间记在请求进入时,再在 finally 里打印整条请求耗时。
真正完整的链路,不只是“进来怎么走”,还包括:结果怎么返回,异常怎么处理,链路结束时谁负责收尾。
12. 工程上最容易踩的坑
12.1 误区一:Filter 和 Interceptor 完全一样
它们都能做“前后拦截”,但工作层级不一样。
12.2 误区二:所有鉴权逻辑都塞进 Interceptor
如果已经使用 Spring Security,很多认证、授权、异常响应更适合交给安全过滤器链统一处理。
12.3 误区三:忘了调用 chain.doFilter
这会导致请求直接卡在当前过滤器,不再继续往后走。
12.4 误区四:以为 DispatcherServlet 是请求第一站
在它之前,通常已经先经过了一层或多层 Filter。
12.5 误区五:把非常重的业务逻辑放在过滤器里
过滤器更适合做入口层的通用处理,不适合承载复杂业务。
13. 一份更实用的落地建议
如果你在 Spring Web 项目里真的要处理请求链路,下面这套习惯通常更稳:
- 跨域、编码、请求包装这类入口问题,优先放在
Filter - 和 Controller 调用强相关的逻辑,优先考虑
Interceptor - 认证、授权、异常响应等安全主线,尽量交给 Spring Security 过滤器链统一处理
- 自定义认证过滤器优先考虑
OncePerRequestFilter - 明确链路顺序,不要把所有逻辑堆进一个入口组件里
14. 总结
把 Spring 里的过滤器、过滤器链、拦截器和 DispatcherServlet 压缩成最核心的几句话,就是:
Filter是更靠近 Servlet 容器入口的通用请求处理机制- 过滤器链解决的是多个入口层逻辑如何按顺序协作
DispatcherServlet是 Spring MVC 的统一调度中心Interceptor是 Spring MVC 内部对 Controller 调用过程的增强机制- Spring Security 之所以强依赖过滤器链,是因为它要在请求进入业务层之前先统一完成安全处理