Spring Boot Filter、Interceptor、AOP 执行顺序深度拆解与生产实践
一、前言
在 Spring Boot 项目开发中,Filter(过滤器)、Interceptor(拦截器)、AOP(切面)是实现请求拦截、权限校验、日志统计、性能监控的核心组件。三者分属不同层级、执行顺序严格区分,也是日常开发 Bug 高发点、面试高频考点。
本文将通过源码层级拆解 + 可运行 Demo + 双场景实测 + 生产规范,彻底讲透三者的执行顺序、核心差异、使用场景及避坑方案。
二、实验环境版本
本次测试基于 Spring Boot3 最新稳定版本,适配 Servlet6.0 新特性,结论适配 Spring Boot3 全系列版本:
| 技术组件 | 对应版本 |
|---|---|
| Spring Boot | 3.2.7 |
| JDK | 21 |
| Servlet API | 6.0(内置 Tomcat 10.1) |
| Spring AOP | 6.1.10 |
三、核心层级与组件定位(洋葱模型)
3.1 整体执行链路
三者遵循由外到内、层层包裹的洋葱模型,完整全链路执行顺序:
Servlet容器层(Filter) ➜ SpringMVC层(Interceptor前置) ➜ Bean代理层(AOP) ➜ Controller ➜ AOP后置 ➜ Interceptor后置 ➜ Filter后置
请求层级洋葱模型可视化:
Plain
# 请求层级洋葱模型
┌────────────────────────────────────────────────────┐
│ Servlet 容器 │
│ ┌──────────────────────────────────────────────┐ │
│ │ Filter 层(Servlet原生) │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ DispatcherServlet 前端控制器 │ │ │
│ │ │ ┌──────────────────────────────────┐ │ │ │
│ │ │ │ Interceptor 层(SpringMVC)│ │ │ │
│ │ │ │ ┌────────────────────────────┐ │ │ │ │
│ │ │ │ │ AOP 层(Bean代理) │ │ │ │ │
│ │ │ │ │ ┌──────────────────────┐ │ │ │ │ │
│ │ │ │ │ │ Controller Bean │ │ │ │ │ │
│ │ │ │ │ └──────────────────────┘ │ │ │ │ │
│ │ │ │ └────────────────────────────┘ │ │ │ │
│ │ │ └──────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
3.2 三大组件核心特性详解
3.2.1 Filter(过滤器)- Servlet容器层
Filter 是Servlet 原生容器级组件,优先级最高,早于 SpringMVC 初始化,由 Tomcat 容器全权管理。
-
层级:最外层,属于 Web 容器原生组件,脱离 Spring 框架依然生效
-
容器归属 :不归 Spring 容器管理,即使加
@Component,也由 Servlet 容器实例化,无法直接 @Autowired 注入业务 Bean -
拦截粒度:粗粒度,仅基于 URL 路径匹配,无法感知 Controller 方法、注解
-
执行特性 :
chain.doFilter()同步阻塞,后置代码一定会执行,不受 SpringMVC 拦截、业务异常影响 -
适用场景:全局编码设置、CORS 跨域、请求流包装、GZIP 压缩、全局安全响应头配置
3.2.2 Interceptor(拦截器)- SpringMVC层
Interceptor 是SpringMVC 核心组件,由 DispatcherServlet 调度,本身是 Spring Bean,可直接使用 Spring 容器所有能力。
-
层级:中间层,Spring 框架专属组件
-
容器归属 :Spring 容器 Bean,支持
@Autowired注入 Service、组件等业务类 -
拦截粒度:细粒度,可精准感知目标 Controller 方法、支持路径匹配、排除、注解拦截
-
生命周期:包含三大核心方法,执行规则严格固定
Interceptor 源码级执行规则:
-
preHandle返回 false:直接终止 MVC 流程,postHandle、afterCompletion均不执行 -
preHandle返回 true:接口正常执行,三个方法全部执行 -
preHandle返回 true + 接口抛异常:postHandle不执行,afterCompletion依然执行
核心作用:业务层拦截,用于登录校验、Token 鉴权、接口限流、业务日志统计、参数校验
3.2.3 AOP(切面)- Spring Bean代理层
AOP 基于 Spring 动态代理机制,是最内层的 Bean 方法拦截组件,粒度最精细。
-
层级:内层,仅作用于 Spring Bean 方法
-
触发条件:必须成功进入 Controller 方法(走完 DispatcherServlet#doDispatch 完整流程)才会生效
-
拦截限制:仅拦截 Spring Bean 方法调用,不感知 Servlet 请求;若被 Interceptor 前置拦截,AOP 完全不执行
-
适用场景:方法级日志、接口耗时统计、事务控制、缓存切面、注解权限拦截、异常统一处理
3.3 极简记忆口诀
Filter 控制「请求能否进入应用」 ,Interceptor 控制「请求能否进入接口方法」 ,AOP 控制「接口方法如何执行」。
四、Demo 项目结构
标准化分层结构,覆盖三大组件完整实现:
Plain
spring-filter-interceptor/
├── pom.xml
└── src/main/java/com/coderplus/
├── Application.java # 项目启动类
├── config/WebConfig.java # 拦截器注册配置(核心必写)
├── controller/OrderController.java # 测试业务接口
├── filter/RequestLoggingFilter.java # 全局请求耗时过滤器
├── interceptor/AuthInterceptor.java # Token鉴权拦截器
└── aspect/LoggingAspect.java # 方法日志切面
五、全组件可运行代码实现
5.1 Filter 全局请求耗时过滤器
实现全局请求耗时统计、请求日志打印,适配所有接口:
Plain
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Order(1)
public class RequestLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String fullPath = req.getRequestURI()
+ (req.getQueryString() != null ? "?" + req.getQueryString() : "");
System.out.println("[Filter] BEFORE: " + fullPath);
long start = System.currentTimeMillis();
// 同步阻塞:等待内层所有MVC、业务逻辑执行完毕
chain.doFilter(request, response);
// 无论内层是否拦截、是否异常,后置代码必定执行
long elapsed = System.currentTimeMillis() - start;
System.out.println("[Filter] AFTER : " + fullPath
+ " | 总耗时 " + elapsed + "ms");
}
}
核心特性 :chain.doFilter() 为同步阻塞方法,SpringMVC 流程中断不会影响 Filter 后置执行。
5.2 Token 鉴权 Interceptor 拦截器
实现接口 Token 校验,无 Token 直接拦截返回 401:
Plain
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
System.out.println("[Interceptor] preHandle: token=" + token);
// 无Token直接拦截,返回401未授权
if (token == null || token.isBlank()) {
System.out.println("[Interceptor] preHandle → 返回 false,拦截请求");
response.setStatus(401);
return false;
}
System.out.println("[Interceptor] preHandle → 返回 true,放行");
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
System.out.println("[Interceptor] postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
String errMsg = ex != null ? " | 异常: " + ex.getMessage() : "";
System.out.println("[Interceptor] afterCompletion" + errMsg);
}
}
5.3 拦截器注册配置
关键知识点 :Filter 加 @Component 自动生效;Interceptor 仅加注解无法生效,必须手动注册到拦截器链!
Plain
import com.coderplus.interceptor.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
// 构造器注入Spring容器中的拦截器Bean
public WebConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 拦截所有业务接口
.excludePathPatterns("/api/health"); // 放行健康检查接口
}
}
5.4 Controller 层 AOP 日志切面
拦截所有 Controller 方法,实现方法执行日志、耗时统计:
Plain
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 切点:拦截所有Controller层业务方法
@Pointcut("execution(* com.coderplus.controller..*(..))")
public void controllerLayer() {}
@Around("controllerLayer()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("[AOP] @Around - 方法: "
+ joinPoint.getSignature().toShortString());
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
System.out.println("[AOP] @Around - 耗时: "
+ (System.currentTimeMillis() - start) + "ms");
return result;
}
}
5.5 测试业务 Controller
Plain
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class OrderController {
@GetMapping("/orders")
public List<Map<String, Object>> getOrders(
@RequestParam(defaultValue = "1") Long userId) {
System.out.println("[Controller] OrderController.getOrders(userId="
+ userId + ")");
return List.of(
Map.of("orderId", "ORD20260709001", "amount", 99.90, "status", "已支付"),
Map.of("orderId", "ORD20260708015", "amount", 256.00, "status", "已发货"),
Map.of("orderId", "ORD20260701042", "amount", 18.50, "status", "已完成")
);
}
}
六、双场景实测:完整执行链路对比
项目启动命令:mvn spring-boot:run,分别测试正常放行请求 和拦截失败请求。
6.1 正常请求(携带Token,完全放行)
测试命令:
Plain
curl http://localhost:8080/api/orders?userId=1 -H "Authorization: Bearer xxx"
控制台完整执行顺序:
-
Filter BEFORE: /api/orders?userId=1
-
Interceptor preHandle: token=Bearer xxx
-
Interceptor preHandle → 返回 true,放行
-
AOP @Around - 方法: OrderController.getOrders(Long)
-
Controller 业务方法执行
-
AOP @Around - 耗时: xxxms
-
Interceptor postHandle
-
Interceptor afterCompletion
-
Filter AFTER : /api/orders?userId=1 | 总耗时 xxxms
标准完整链路:Filter前置 ➜ Interceptor前置(preHandle) ➜ AOP ➜ Controller ➜ AOP后置 ➜ Interceptor后置(postHandle/afterCompletion) ➜ Filter后置
6.2 非法请求(无Token,被拦截401)
测试命令:
Plain
curl http://localhost:8080/api/orders?userId=1
控制台执行结果:
-
Filter BEFORE: /api/orders?userId=1
-
Interceptor preHandle: token=null
-
Interceptor preHandle → 返回 false,拦截请求
-
Filter AFTER : /api/orders?userId=1 | 总耗时 1ms
6.3 核心现象总结(线上BUG根源)
-
❌ AOP 完全不执行:请求未进入 Controller 方法,动态代理无法触发
-
❌ Interceptor 后置方法失效:preHandle=false 直接截断 MVC 流程,postHandle、afterCompletion 均不执行
-
✅ Filter 后置代码必执行:Servlet 容器生命周期独立,不受 SpringMVC 拦截、异常中断影响
七、Filter 与 Interceptor 生产选型对比
| 对比维度 | Filter | Interceptor |
|---|---|---|
| 所属层级 | Servlet 容器层(原生) | SpringMVC 框架层 |
| Spring Bean 注入 | 不归Spring容器管理,无法直接注入 | Spring原生Bean,支持@Autowired注入 |
| 接口感知能力 | 仅识别URL,无法感知Controller方法 | 精准感知Handler、方法、注解 |
| 拦截粒度 | 粗粒度,仅URL匹配 | 细粒度,支持方法/注解精准拦截 |
| 核心适用场景 | 全局基础处理:编码、CORS、流包装、压缩、安全头 | 业务逻辑处理:权限、登录、限流、业务日志、指标统计 |
八、开发规范 & 面试核心总结
8.1 执行顺序铁律(必背)
Filter前置 ➜ Interceptor.preHandle ➜ AOP ➜ Controller ➜ AOP后置 ➜ Interceptor后置 ➜ Filter后置
8.2 高频踩坑点
-
Interceptor
preHandle=false仅终止 MVC 流程,不会终止 Filter 后置执行,极易造成监控数据失真 -
AOP 触发硬性条件:必须成功进入 Controller 方法,被 Interceptor 拦截的请求,AOP 完全失效
-
Interceptor 不能仅靠
@Component生效,必须手动注册到 MVC 拦截器链 -
Filter 加
@Component仅注册到 Servlet 容器,不属于 Spring Bean,无法直接注入业务类
8.3 生产强制开发规范
-
接口有效请求统计、QPS、成功率、耗时打点,禁止写在 Filter 后置
-
优先使用 Interceptor
afterCompletion做业务指标统计、日志收尾 -
afterCompletion源码规则:preHandle=true时,正常执行走 postHandle+afterCompletion,异常仅走 afterCompletion,是生产统计最优位置