跳至正文
来两杯美式
返回

Filter、Interceptor 与 Aspect:三个请求拦截层次详解

By 来两杯美式
发布于

这是一篇 2019 年 1 月整理的笔记。内容按当年的视角写就:javax.servlet 命名空间、Spring Boot 2.x、Servlet 3.0 注解式 Filter——还没有 Spring Boot 3 的 jakarta.* 迁移。示例代码保持当年可直接运行的风格。

一个 HTTP 请求从浏览器发出到业务方法执行,会依次经过多个拦截层次。Spring 生态里最常被问到也最容易被搞混的,就是 Filter、Interceptor、Aspect 这三兄弟:它们都在”请求进来之后、业务执行之前”做点事情,但底层机制、生效范围、能用什么能力,完全不同。

三者对比一览

维度FilterInterceptorAspect
所属框架Servlet 规范Spring MVCSpring AOP
生效层级Servlet 容器Spring MVC(DispatcherServlet 内部)Spring 容器内的 Bean 方法
实现机制容器回调拦截器链动态代理(JDK / CGLIB)
能否注入 Bean不行(脱离容器)可以可以
作用范围所有 Servlet 请求(含静态资源)匹配到的 Controller 请求匹配到的 Spring Bean 方法
典型场景编码、跨域、日志、鉴权登录校验、权限、请求耗时事务、缓存、日志、参数校验
执行次数每次请求 1 次每次匹配请求 1 次每次匹配方法调用 1 次

关键差异就一句话:

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 ...");
    }
}

三个方法的执行时机:

  1. preHandle → Controller 方法前,返回 false拦截请求(如未登录跳转登录页)。
  2. postHandle → Controller 方法后、视图渲染前,可以往 ModelAndView 里塞公共数据。
  3. 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 做登录鉴权,Aspect 做方法级日志和事务——各司其职,互不干扰。


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制

上一篇
MySQL 知识系列(一):整体架构与存储引擎
下一篇
Dubbo 服务拆分与架构设计:子系统划分的四大原则