这是一篇 2019 年 1 月整理的笔记。内容按当年的视角写就:
javax.servlet命名空间、Spring Boot 2.x、Servlet 3.0 注解式 Filter——还没有 Spring Boot 3 的jakarta.*迁移。示例代码保持当年可直接运行的风格。
一个 HTTP 请求从浏览器发出到业务方法执行,会依次经过多个拦截层次。Spring 生态里最常被问到也最容易被搞混的,就是 Filter、Interceptor、Aspect 这三兄弟:它们都在”请求进来之后、业务执行之前”做点事情,但底层机制、生效范围、能用什么能力,完全不同。
三者对比一览
| 维度 | Filter | Interceptor | Aspect |
|---|---|---|---|
| 所属框架 | Servlet 规范 | Spring MVC | Spring AOP |
| 生效层级 | Servlet 容器 | Spring MVC(DispatcherServlet 内部) | Spring 容器内的 Bean 方法 |
| 实现机制 | 容器回调 | 拦截器链 | 动态代理(JDK / CGLIB) |
| 能否注入 Bean | 不行(脱离容器) | 可以 | 可以 |
| 作用范围 | 所有 Servlet 请求(含静态资源) | 匹配到的 Controller 请求 | 匹配到的 Spring Bean 方法 |
| 典型场景 | 编码、跨域、日志、鉴权 | 登录校验、权限、请求耗时 | 事务、缓存、日志、参数校验 |
| 执行次数 | 每次请求 1 次 | 每次匹配请求 1 次 | 每次匹配方法调用 1 次 |
关键差异就一句话:
- Filter 依赖 Servlet 容器,是 Servlet 规范的一部分;Interceptor 是 Spring MVC 自己的组件,独立于 Servlet 容器。
- Filter 的执行由 Servlet 容器回调完成;Interceptor 通过动态代理方式执行。
- Filter 的生命周期由 Servlet 容器管理;Interceptor 由 IoC 容器管理,因此可以通过注入获取其他 Bean 的实例,使用更方便。
Filter:Servlet 容器层的过滤器
Filter 属于 Servlet 规范,作用于所有经过容器的请求——也就是说,在 Spring MVC 介入之前它就已经执行了,静态资源、404 页面它都能拦到。
方式一:实现 javax.servlet.Filter + @Component
@Component
public class TestUseFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("TestUseFilter begin ...");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("TestUseFilter end ...");
}
}
⚠️ 用
@Component注册的 Filter,默认匹配所有 URL(/*)。如果想限定路径,用下面两种方式。
方式二:@WebFilter + @ServletComponentScan(推荐)
Spring Boot 下更规范的做法是用 Servlet 3.0 注解 + 启动类扫描:
@WebFilter(urlPatterns = "/api/*", filterName = "testUseFilter")
public class TestUseFilter implements Filter {
// doFilter 同上
}
@SpringBootApplication
@ServletComponentScan // 扫描 @WebFilter / @WebServlet / @WebListener
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
方式三:FilterRegistrationBean(注册第三方 jar 中的 Filter)
引入三方 jar 包里的 Filter 时,往往没法改它的源码加注解,这时用 FilterRegistrationBean 手动注册最合适:
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean testUserFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new TestUseFilter());
bean.setUrlPatterns(Arrays.asList("/*"));
bean.setOrder(1); // 数字越小越先执行,多个 Filter 时控制顺序
return bean;
}
}
多个 Filter 并存时,执行顺序由
setOrder()控制:顺序号小的先执行 doFilter,后执行返回逻辑——像个洋葱,层层包裹。
Filter 的局限
Filter 在 Servlet 容器层面,拿不到 Spring 的 HandlerMethod——也就是不知道这个请求最终会打到哪个 Controller 的哪个方法。而且 @Component 注册的 Filter 由容器实例化,虽然后续可以被注入(Spring Boot 做了适配),但规范层面 Filter 并不依赖 IoC,最佳实践仍是把业务逻辑委托给注入的 Service。
Interceptor:Spring MVC 层的拦截器
Interceptor 是 Spring MVC 的组件,作用于 DispatcherServlet 分发请求的流程中。它能看到请求要执行的 HandlerMethod,可以拿到 Controller 的类和方法,还能自由注入任意 Spring Bean。
实现 HandlerInterceptor
@Component
public class LoginInterceptor implements HandlerInterceptor {
/**
* Controller 方法执行之前。返回 false 则中断请求,不再往下执行。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle: " + handler);
return true;
}
/**
* Controller 方法正常执行之后、视图渲染之前。
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ...");
}
/**
* 请求完成后(含异常),无论 Controller 是否正常返回都会执行,常用于清理资源。
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion ...");
}
}
三个方法的执行时机:
preHandle→ Controller 方法前,返回false可拦截请求(如未登录跳转登录页)。postHandle→ Controller 方法后、视图渲染前,可以往ModelAndView里塞公共数据。afterCompletion→ 整个请求完成(含异常)后,类似finally,适合记录耗时、释放资源。
注册拦截器(WebMvcConfigurer)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/register", "/static/**"); // 放行路径
}
}
排除路径一定要包含静态资源和登录入口,否则会出现”死循环跳登录页”的经典事故。
为什么 Interceptor 能注入 Bean?
因为 Interceptor 的注册是在 WebMvcConfigurer(Spring 容器内的配置类)中完成的,loginInterceptor 通过 @Autowired 注入,本质是一个由 IoC 容器管理的单例 Bean——所以 preHandle 里可以直接用任何注入进来的 Service。这就是它比 Filter “方便”的地方。
Aspect:Spring AOP 层的切面
Aspect 再往深一层:它不关心 HTTP 请求,而是直接作用于 Spring 容器内任意 Bean 的方法调用。通过动态代理(JDK 动态代理或 CGLIB),把切面逻辑织入到目标方法的前后。
实现一个切面
@Aspect
@Component
public class LogAspect {
/**
* 定义切入点:com.example.service 包下所有类的所有方法。
* execution 表达式:修饰符 返回类型 包.类.方法(参数)
*/
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {
}
/**
* 环绕通知:在目标方法执行前后做增强。
*/
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("Aspect before ... " + joinPoint.getSignature());
Object result = joinPoint.proceed(); // 调用目标方法
long cost = System.currentTimeMillis() - start;
System.out.println("Aspect after ... cost " + cost + "ms");
return result;
}
}
AOP 的几种通知类型:
| 注解 | 时机 | 典型用途 |
|---|---|---|
@Before | 目标方法执行前 | 参数校验、权限判断 |
@After | 目标方法执行后(含异常) | 清理资源 |
@AfterReturning | 目标方法正常返回后 | 记录返回值 |
@AfterThrowing | 目标方法抛异常后 | 异常通知、告警 |
@Around | 目标方法前后(最强) | 事务、耗时统计、缓存 |
切入点表达式速查
execution(public * com.example.service.UserService.*(..))
execution(* com.example.service..*.*(..)) // 包及子包所有方法
execution(* com.example.service.*.get*(..)) // get 开头的方法
within(com.example.service.*) // 包内所有类型
@annotation(com.example.annotation.Log) // 标注了 @Log 的方法
Aspect 的局限
AOP 只能切到 Spring 容器管理的 Bean,且方法调用必须走代理(从外部调用)。类内部 this.method() 自调用不会经过代理,这是 AOP 失效的高频坑;另外它拦不到静态资源、拦不到容器层面的请求。
三者在一次请求中的完整执行顺序
一个请求打到 Spring Boot 应用,完整链路是这样的:
请求进入
↓
Filter.doFilter 前 ← Servlet 容器层:编码、跨域、全局日志
↓
DispatcherServlet 分发
↓
Interceptor.preHandle ← Spring MVC 层:登录校验、权限
↓
Aspect @Around/@Before ← AOP 层:事务、方法级日志
↓
Controller 业务方法
↓
Aspect @After/@AfterReturning
↓
Interceptor.postHandle
↓
Interceptor.afterCompletion
↓
Filter.doFilter 后
↓
返回响应
用一句话概括:Filter 管得最宽(整个请求),Interceptor 管 Controller 之前之后,Aspect 管具体的方法调用。
选型建议
- 跨域、编码、请求/响应包装、静态资源拦截 → Filter(作用范围最广,容器层面)。
- 登录态校验、权限、请求耗时、通用参数 → Interceptor(能拿到 HandlerMethod,能注入 Bean,最常用)。
- 事务、缓存、方法级日志、埋点、参数脱敏 → Aspect(针对业务方法,粒度最细)。
三者不是互斥的,实际项目中往往三层同时存在:Filter 做编码和跨域,Interceptor 做登录鉴权,Aspect 做方法级日志和事务——各司其职,互不干扰。