Spring Boot请求与Bean生命周期实战:Filter、Interceptor、AOP执行顺序全解析

Spring Boot请求与Bean生命周期实战:Filter、Interceptor、AOP执行顺序全解析 先看一句词花自飘零水自流。很多人第一次读到它是在李清照的《一剪梅》里。初读觉得是写离别写思念拿来做技术比喻反而有另一层意味——系统里的数据、对象、请求很多都不在静态的状态里而是在不停地流转请求从客户端出发依次穿过过滤器、拦截器、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 version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent groupIdcom.example/groupId artifactIdlifecycle-demo/artifactId version0.0.1-SNAPSHOT/version namelifecycle-demo/name description生命周期与请求流转示例/description properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /project2.2 配置文件在src/main/resources/application.properties中配置应用名称和 H2 数据源spring.application.namelifecycle-demo spring.datasource.urljdbc:h2:mem:demo;DB_CLOSE_DELAY-1 spring.datasource.driver-class-nameorg.h2.Driver spring.datasource.usernamesa spring.datasource.password spring.h2.console.enabledtrue配置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.properties2.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 执行视图与请求收尾); } }三个方法的作用preHandleController 方法执行前调用。返回 true 表示继续执行返回 false 表示中断请求。postHandleController 方法执行后、视图渲染前调用适合修改 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?namecsdn控制台会输出类似下面的内容[Filter] 请求进入/api/hello [Interceptor] preHandle 执行准备进入 Controller [AOP] 方法进入DemoController.hello() [Controller] 进入