☰
Spring Boot请求与Bean生命周期实战:Filter、Interceptor、AOP执行顺序全解析
2026/9/27 22:54:43 网站建设 项目流程

先看一句词:花自飘零水自流。很多人第一次读到它,是在李清照的《一剪梅》里。初读觉得是写离别,写思念;拿来做技术比喻,反而有另一层意味——系统里的数据、对象、请求,很多都不在静态的状态里,而是在不停地流转:请求从客户端出发,依次穿过过滤器、拦截器、AOP、Controller,最后再原路返回;一个 Bean 从构造、赋值、初始化到销毁,有一套不会随意改变的先后顺序;一条业务事件从被发布到被监听,可能同步完成,也可能等到事务提交后再异步处理。花什么时候落,水往哪里流,就像这些生命周期,并不完全由我们控制,但可以通过配置、组件设计和容错手段,让它们尽量符合预期。

这篇文章是一个“合集”式的实战笔记,包含三条主线:

  • 第一回:HTTP 请求的生命周期流转,讲清楚 Filter、Interceptor、AOP、ControllerAdvice 的执行顺序与协作方式;
  • 第二回:Spring Bean 生命周期,从构造器到销毁方法,把常见的初始化回调排一遍;
  • 第三回:Spring 事件机制,包括同步监听、事务监听和异步监听,理解事件是怎么从发布者流向订阅者的;
  • 最后补充常见异常、排查思路和工程移植建议。

适合的读者包括:想梳理 Spring MVC 请求链路的同学、对 Bean 生命周期只停留在“知道”但没完整验证过的开发者,以及在项目里用事件解耦但经常踩监听器不触发的朋友。

写作示例代码以 Spring Boot 为基础,每个内容都有完整代码,可以直接复制为一个演示工程。

1. 背景与核心概念

先明确一个容易被忽略的事实:Spring Boot 应用启动后,并不是“请求进来,方法执行完,就结束”这么简单。在请求到达真正的业务方法之前,Spring MVC 会做很多事;在方法返回之后,容器还会做大量收尾工作。理解这些“流转顺序”,是排查 filter 失效、拦截器不生效、AOP 切不中用、异步事件丢消息等问题的前提。

如果用“花自飘零水自流”来对应这三类流转,可以这样理解:

  • 请求像流水:水从源头流入河道,经过水闸、弯道、浅滩,最后汇入湖海。HTTP 请求也会从客户端流入 Servlet 容器,经过 Filter、Interceptor、AOP,最终进入 Controller 方法。
  • Bean 像花木:花苞长出、绽放、凋谢,每一个阶段都有自然规律。Spring 管理的 Bean 也遵循“实例化 -> 属性填充 -> 初始化 -> 使用 -> 销毁”的周期。
  • 事件像随风飘散的花瓣:事件被发布者抛出后,并不总是当场被处理,它可能被同步监听,也可能排队等待事务提交后再异步处理。

这些机制并不复杂,难点在于顺序和触发条件。很多开发者的困惑点,比如“AOP 和拦截器到底谁先执行”“@PostConstruct 和 afterPropertiesSet 有什么区别”“为什么事务事件不触发”,本质上是不清楚执行的编排顺序。

还有一类容易混淆的概念,这里先做一个简单区分:

  • Filter 是 Servlet 层面的组件,不属于 Spring MVC,它依赖 Servlet 容器,在请求进入 DispatcherServlet 之前就执行。
  • Interceptor 是 Spring MVC 的组件,基于 HandlerMapping,可以拿到 HandlerMethod 信息,能做登录校验、权限判断、日志记录。
  • AOP 是 Spring 的切面能力,可以拦截 Spring 管理的 Bean 方法,包括 Controller、Service 等,是比 Interceptor 更上层、更接近业务的切面方式。

一句话概括:Filter 管最早的入口,Interceptor 管进入 Controller 前的准备,AOP 管方法执行前后的逻辑增强。三者配合,就能覆盖一个请求从最外层到业务方法内部的完整链路。

2. 环境准备与版本说明

下面的示例代码基于 Spring Boot 3.x、JDK 17 编写,使用 Maven 构建。版本不需要完全一致,核心思路是通用的;如果你还在用 Spring Boot 2.x,注意部分包名会从javax.servlet变为jakarta.servlet,本文示例以 Spring Boot 3.x 为准。

2.1 基础依赖

在pom.xml中引入 web、aop、jdbc 和 H2 内存数据库。引入 jdbc 和 H2 的目的是为了演示事务事件,如果你只验证请求链路,去掉这两个依赖也没问题。

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.5</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>lifecycle-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>lifecycle-demo</name> <description>生命周期与请求流转示例</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

2.2 配置文件

在src/main/resources/application.properties中配置应用名称和 H2 数据源:

spring.application.name=lifecycle-demo spring.datasource.url=jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1 spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true

配置DB_CLOSE_DELAY=-1是为了避免内存数据库在连接关闭后自动销毁数据,这个设置常用于演示场景。生产环境不要照搬内存数据库,这里只是为了验证事务与事件机制。

2.3 项目结构

建议按下面的目录结构组织代码,后面讲到哪个类,就放到对应的包下:

lifecycle-demo ├── pom.xml └── src/main ├── java/com/example/lifecycle │ ├── LifecycleDemoApplication.java │ ├── advice │ │ └── GlobalExceptionHandler.java │ ├── aspect │ │ └── ControllerTimeAspect.java │ ├── bean │ │ ├── LifecycleBean.java │ │ └── TraceBeanPostProcessor.java │ ├── config │ │ └── WebConfig.java │ ├── controller │ │ └── DemoController.java │ ├── event │ │ └── OrderEvent.java │ ├── filter │ │ └── RequestLogFilter.java │ ├── interceptor │ │ └── TraceInterceptor.java │ ├── listener │ │ ├── OrderEventListener.java │ │ └── OrderTransactionListener.java │ └── service │ └── OrderService.java └── resources └── application.properties

2.4 启动类

启动类上加上@EnableAsync,后面演示异步事件监听时必须要开启异步支持。

package com.example.lifecycle; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class LifecycleDemoApplication { public static void main(String[] args) { SpringApplication.run(LifecycleDemoApplication.class, args); } }

3. 第一回:请求如何流过过滤器、拦截器与 AOP

这一回是整个合集的重点。一个 HTTP 请求进入 Spring Boot 应用后,会沿着一条很清晰的路径流动。下面我们用一个简单接口来观察链路顺序。

3.1 过滤器:请求进入的第一道门

过滤器属于 Servlet 容器层面的组件,它的特点是:在整个请求转发链的最前面执行。你可以把它理解为系统入口处的门卫,不管请求要访问哪个 Controller,都会先经过这里。

先定义一个过滤器,记录请求进入和结束的时间:

package com.example.lifecycle.filter; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; 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 RequestLogFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; long startTime = System.currentTimeMillis(); System.out.println("[Filter] 请求进入:" + httpServletRequest.getRequestURI()); chain.doFilter(request, response); System.out.println("[Filter] 请求结束,总耗时:" + (System.currentTimeMillis() - startTime) + " ms"); } }

关键点在于chain.doFilter(request, response)。这行代码的意思是:当前过滤器处理完后,把请求继续传递给过滤器链中的下一个节点。如果这里不调用,请求就会被拦截,无法到达后面的 Controller。

@Order(1)控制多个过滤器之间的执行顺序。数字越小,越先执行。这里只有一个过滤器,所以顺序并不重要,但建议在实际项目中显式声明,避免多个过滤器堆叠时顺序混乱。

3.2 拦截器:进入 Controller 之前的第二道关卡

拦截器属于 Spring MVC 组件,它不像 Filter 那样依赖 Servlet 容器,而是依赖 HandlerMapping。拦截器能拿到HandlerMethod,因此可以判断当前请求要访问的是哪个 Controller 的哪个方法。

定义一个拦截器,在 preHandle、postHandle、afterCompletion 三个阶段分别打印日志:

package com.example.lifecycle.interceptor; 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 TraceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("[Interceptor] preHandle 执行,准备进入 Controller"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("[Interceptor] postHandle 执行,Controller 已返回"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("[Interceptor] afterCompletion 执行,视图与请求收尾"); } }

三个方法的作用:

  • preHandle:Controller 方法执行前调用。返回 true 表示继续执行,返回 false 表示中断请求。
  • postHandle:Controller 方法执行后、视图渲染前调用,适合修改 ModelAndView。
  • afterCompletion:整个请求完成后调用,适合做资源清理和日志收尾。

拦截器必须注册到WebMvcConfigurer中才能生效:

package com.example.lifecycle.config; import com.example.lifecycle.interceptor.TraceInterceptor; import org.springframework.beans.factory.annotation.Autowired; 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 { @Autowired private TraceInterceptor traceInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(traceInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/error"); } }

注意addPathPatterns("/api/**"),这意味着只有/api下的请求才会经过拦截器。如果想拦截所有请求,可以写/**。这里保留/api/**,是为了和后面的 Controller 路径呼应。

3.3 AOP 切面:直接增强业务方法

拦截器虽然能感知到 Controller 方法,但拿不到方法参数的具体语义,也不适合做细粒度的业务切面。AOP 可以做到这一点。AOP 是 Spring 面向切面编程的能力,它可以拦截任意 Spring Bean 的方法,通过在方法前后增加逻辑,实现日志、权限、性能统计等功能。

先定义一个切面,环绕 Controller 方法执行并打印耗时:

package com.example.lifecycle.aspect; 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 ControllerTimeAspect { @Pointcut("execution(public * com.example.lifecycle.controller..*.*(..))") public void controllerMethods() { } @Around("controllerMethods()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); System.out.println("[AOP] 方法进入:" + joinPoint.getSignature().toShortString()); try { return joinPoint.proceed(); } finally { System.out.println("[AOP] 方法退出,耗时:" + (System.currentTimeMillis() - start) + " ms"); } } }

切点表达式execution(public * com.example.lifecycle.controller..*.*(..))表示:拦截controller包及其子包下所有 public 方法,方法参数任意,返回值任意。@Around注解表示环绕通知,能在方法执行前后做统一处理。

AOP 和拦截器的最大区别是粒度不同。拦截器通常做 Web 层通用处理,AOP 可以做更细的方法级增强,甚至能拿到方法的参数和返回值,适合做统一校验、记录操作日志、统计接口耗时等。

3.4 ControllerAdvice:兜底处理异常的“收容所”

请求链路中还有一种比较特殊的存在,就是全局异常处理。当 Controller 方法抛出异常时,如果不做处理,用户会收到一堆堆栈信息,既不好看,也可能暴露系统内部细节。@RestControllerAdvice可以统一捕获异常并转换返回结果。

定义一个最简单的全局异常处理:

package com.example.lifecycle.advice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public String handleException(Exception e) { System.out.println("[Advice] 捕获异常:" + e.getMessage()); return "统一异常处理:" + e.getMessage(); } }

这里为了演示,捕获了所有Exception,并直接返回异常信息。实际项目中不建议把异常信息直接返回给前端,应该转换成统一的响应对象,比如Result、ApiResponse之类的结构。

3.5 配置 Controller 并观察执行链路

现在定义一个 Controller,包含两个接口。一个 hello 接口验证请求链路,一个创建订单接口用来验证事务事件。

package com.example.lifecycle.controller; import com.example.lifecycle.service.OrderService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") public class DemoController { private final OrderService orderService; public DemoController(OrderService orderService) { this.orderService = orderService; } @GetMapping("/hello") public String hello(@RequestParam(required = false) String name) { System.out.println("[Controller] 进入 hello 接口"); return "hello " + (name == null ? "anonymous" : name); } @PostMapping("/order") public String createOrder(@RequestParam Long orderId) { System.out.println("[Controller] 进入创建订单接口:" + orderId); orderService.createOrderWithTx(orderId); return "订单创建成功:" + orderId; } }

启动应用后,访问http://localhost:8080/api/hello?name=csdn,控制台会输出类似下面的内容:

[Filter] 请求进入:/api/hello [Interceptor] preHandle 执行,准备进入 Controller [AOP] 方法进入:DemoController.hello() [Controller] 进入

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询