AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的核心特性之一,它通过横切关注点分离的设计思想,将日志记录、性能监控、事务管理等通用功能与核心业务逻辑解耦,极大提升了代码的复用性和可维护性。本文将从实战使用、实现步骤、底层原理三个维度,全面解析 Spring Boot AOP 的工作机制。
一、基本使用(实战示例)
Spring Boot 对 AOP 进行了自动配置优化,开发者只需简单三步即可快速集成 AOP 功能,以下是完整可运行的实战案例。
1. 引入依赖
Spring Boot 提供了专门的 AOP starter 依赖,无需手动指定版本(推荐使用 Spring Boot 统一版本管理):
<!-- Spring Boot AOP starter 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<!-- 若项目已引入 spring-boot-starter-parent,可省略 version 标签 -->
</dependency>
说明:
spring-boot-starter-aop底层依赖了aspectjweaver(AOP 织入器)和spring-aop` 核心包,无需额外引入。
2. 定义切面(核心组件)
切面(Aspect)是 AOP 的核心载体,包含切点(Pointcut) 和通知(Advice) 两部分:
- 切点:定义 “拦截哪些方法”(通过表达式指定)
- 通知:定义 “拦截后做什么”(前置、后置、环绕等动作)
/**
* 示例切面:日志记录与方法监控
* 注意:类名建议遵循 Aspect 后缀规范(如 LogAspect),提高可读性
*/
@Component // 标识为 Spring 组件,使其被容器扫描
@Aspect // 标识为切面类,Spring 会识别并解析其中的切点和通知
public class LogAspect {
/**
* 定义切点:指定拦截规则
* execution 表达式语法:execution(访问修饰符? 返回值 包名.类名.方法名(参数列表) 异常类型?)
* 示例:拦截 com.example.demo.service 包下所有类的所有公共方法
*/
@Pointcut("execution(public * com.example.demo.service..*(..))")
public void serviceMethodPointCut() {} // 切点方法:仅作为切点标识,无实际逻辑
/**
* 前置通知:目标方法执行前执行
* 关联切点:通过切点方法名引用
*/
@Before("serviceMethodPointCut()")
public void beforeAdvice() {
System.out.println("[前置通知] 方法即将执行...");
}
/**
* 环绕通知:目标方法执行前后均可干预,支持修改入参、返回值
* 核心参数:ProceedingJoinPoint 可获取目标方法信息(类名、方法名、参数等),并触发目标方法执行
*/
@Around("serviceMethodPointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 目标方法执行前逻辑(如记录开始时间、打印入参)
long startTime = System.currentTimeMillis();
System.out.printf("[环绕通知-前] 目标方法:%s,入参:%s%n",
joinPoint.getSignature().getName(), // 获取方法名
joinPoint.getArgs()); // 获取方法参数
// 2. 执行目标方法(必须调用 proceed(),否则目标方法不会执行)
Object result = joinPoint.proceed(); // 接收目标方法返回值
// 3. 目标方法执行后逻辑(如计算耗时、打印返回值)
long costTime = System.currentTimeMillis() - startTime;
System.out.printf("[环绕通知-后] 目标方法执行完成,耗时:%dms,返回值:%s%n",
costTime, result);
return result; // 返回目标方法结果(可修改后返回)
}
/**
* 后置通知:目标方法执行后执行(无论是否抛出异常)
*/
@After("serviceMethodPointCut()")
public void afterAdvice() {
System.out.println("[后置通知] 方法执行完成(无论是否异常)...");
}
/**
* 返回通知:目标方法正常返回后执行(异常时不执行)
* 可通过 returning 参数接收目标方法返回值
*/
@AfterReturning(value = "serviceMethodPointCut()", returning = "result")
public void afterReturningAdvice(Object result) {
System.out.printf("[返回通知] 方法正常返回,返回值:%s%n", result);
}
/**
* 异常通知:目标方法抛出异常后执行(正常返回时不执行)
* 可通过 throwing 参数接收异常对象
*/
@AfterThrowing(value = "serviceMethodPointCut()", throwing = "e")
public void afterThrowingAdvice(Exception e) {
System.out.printf("[异常通知] 方法抛出异常,异常信息:%s%n", e.getMessage());
}
}
3. 启用 AOP 自动代理
Spring Boot 2.0+ 版本中,@EnableAspectJAutoProxy 注解可省略(spring-boot-starter-aop 已默认启用),若需手动控制或兼容低版本,可在启动类添加该注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 应用启动类
* @EnableAspectJAutoProxy:启用 AspectJ 注解驱动的 AOP 自动代理(可选)
* proxyTargetClass = true:强制使用 CGLIB 动态代理(默认 false,优先 JDK 动态代理)
* exposeProxy = true:暴露代理对象到 ThreadLocal,可通过 AopContext.currentProxy() 获取
*/
@SpringBootApplication
// @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
4. 测试验证
编写一个 Service 类,触发 AOP 拦截:
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getUserInfo(String userId) {
System.out.println("[核心业务] 查询用户信息,用户ID:" + userId);
// 模拟正常返回(可注释此行,取消注释下方异常代码测试异常通知)
return "用户信息:" + userId;
// 模拟抛出异常
// throw new RuntimeException("查询用户信息失败");
}
}
启动应用后,调用 userService.getUserInfo("1001"),控制台输出如下(通知执行顺序清晰可见):
[前置通知] 方法即将执行...
[环绕通知-前] 目标方法:getUserInfo,入参:[1001]
[核心业务] 查询用户信息,用户ID:1001
[环绕通知-后] 目标方法执行完成,耗时:2ms,返回值:用户信息:1001
[后置通知] 方法执行完成(无论是否异常)...
[返回通知] 方法正常返回,返回值:用户信息:1001
二、AOP 核心实现步骤
Spring AOP 的底层实现遵循 “代理对象生成 + 拦截逻辑执行” 的核心流程,整体可分为 3 个关键步骤:
1. 扫描并获取目标 Bean
Spring 是基于 Bean 的容器,AOP 拦截的本质是对 “目标 Bean” 生成代理对象。Spring 通过Bean 生命周期事件,在 Bean 初始化完成后拦截并获取所有 Bean 实例,为后续代理做准备。
2. 筛选需代理的 Bean(切点匹配)
并非所有 Bean 都需要被代理,Spring 会根据切面中定义的切点表达式(如 execution(* com.example.demo.service..*(..))),逐一验证 Bean 的方法是否符合拦截规则,筛选出需要被代理的目标 Bean。
3. 封装拦截逻辑(通知 -> 拦截器)
Spring 不会直接执行 @Before、@Around 等注解标记的方法,而是将这些通知逻辑封装为MethodInterceptor(方法拦截器) 对象,并与切点信息组合成 PointcutAdvisor(切点顾问)。每个切面最终会被解析为一个 List<PointcutAdvisor> 集合,存储拦截规则和对应的执行逻辑。
三、底层原理:每一步如何实现?
Spring AOP 的核心实现类是 AnnotationAwareAspectJAutoProxyCreator(BeanPostProcessor 接口的实现类),它贯穿了 “获取 Bean -> 筛选 Bean -> 生成代理” 的全流程。以下从源码角度拆解关键步骤:
1. 核心组件的注册:@EnableAspectJAutoProxy 的作用
@EnableAspectJAutoProxy 是启用 AOP 的 “开关”,其底层通过 @Import 注解引入 AspectJAutoProxyRegistrar 类,最终向 Spring 容器注册 AnnotationAwareAspectJAutoProxyCreator 实例。
graph TD
A["@EnableAspectJAutoProxy 注解"] -->|"触发导入"| B["@Import(AspectJAutoProxyRegistrar.class)"]
B -->|"执行注册逻辑"| C["AspectJAutoProxyRegistrar 类"]
C -->|"调用工具方法"| D["AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary()"]
D -->|"向容器注册Bean"| E["AnnotationAwareAspectJAutoProxyCreator 实例"]
AnnotationAwareAspectJAutoProxyCreator 的核心能力:
- 实现
BeanPostProcessor:可在 Bean 初始化前后干预 Bean 的创建过程 - 实现
AspectJAdvisorFactory:可解析@Aspect注解,生成切点和通知对象
2. 筛选需代理的 Bean:切点匹配流程
AnnotationAwareAspectJAutoProxyCreator 通过重写 BeanPostProcessor 的 postProcessAfterInitialization 方法,在 Bean 初始化完成后执行筛选逻辑:
graph TD
A["postProcessAfterInitialization(Bean初始化后)"] -->|"判断是否需要代理"| B["wrapIfNecessary(核心方法)"]
B -->|"获取适用于当前Bean的顾问"| C["getAdvicesAndAdvisorsForBean()"]
C -->|"筛选合格的顾问"| D["findEligibleAdvisors()"]
D -->|"匹配切点与Bean方法"| E["findAdvisorsThatCanApply()"]
E -->|"工具类校验"| F["AopUtils.findAdvisorsThatCanApply()"]
F -->|"最终匹配逻辑"| G["canApply(Pointcut, 目标类, 目标方法)"]
G -->|"匹配成功"| H["标记为需代理Bean"]
G -->|"匹配失败"| I["直接返回原Bean"]
关键逻辑说明:
canApply方法会解析切点表达式,判断目标 Bean 的方法是否符合拦截规则- 支持多种切点表达式(如
execution、@annotation、within等),底层通过 AspectJ 提供的解析工具实现
3. 封装拦截逻辑:通知 -> 拦截器的转换
@Before、@Around 等通知注解不会直接被执行,而是通过以下流程转换为可执行的拦截器:
graph TD
A["postProcessAfterInitialization"] -->|"触发顾问查找"| B["wrapIfNecessary"]
B -->|"获取顾问列表"| C["getAdvicesAndAdvisorsForBean()"]
C -->|"查找候选顾问"| D["findEligibleAdvisors()"]
D -->|"解析@Aspect切面"| E["findCandidateAdvisors()"]
E -->|"构建切面顾问"| F["BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors()"]
F -->|"转换通知为拦截器"| G["AbstractAspectJAdvisorFactory.getAdvisor()"]
G -->|"生成MethodInterceptor"| H["@Before -> AspectJMethodBeforeAdvice<br>@Around -> AspectJAroundAdvice<br>@After -> AspectJAfterAdvice"]
H -->|"组合切点与拦截器"| I["PointcutAdvisor 对象"]
核心对象关系
PointcutAdvisor、MethodInterceptor、Pointcut 三者是 “聚合关系”,共同承载拦截逻辑:
graph TD
A["PointcutAdvisor(切点顾问)"] -->|"包含"| B["Pointcut(切点):定义拦截规则"]
A -->|"包含"| C["MethodInterceptor(方法拦截器):封装通知逻辑"]
C -->|"执行"| D["目标方法(被代理的核心业务方法)"]
4. 生成代理对象:JDK 动态代理 vs CGLIB 动态代理
当筛选出需代理的 Bean 并封装好拦截器后,AnnotationAwareAspectJAutoProxyCreator 会调用 ProxyFactory 生成代理对象,代理方式分为两种:
| 代理方式 | 适用场景 | 核心原理 |
|---|---|---|
| JDK 动态代理 | 目标类实现了接口 | 基于 java.lang.reflect.Proxy,生成接口的代理实例 |
| CGLIB 动态代理 | 目标类未实现接口(或指定 proxyTargetClass = true) | 基于字节码增强,生成目标类的子类作为代理实例 |
| 代理对象的执行流程: |
- 调用代理对象的方法 → 触发
MethodInterceptor拦截器 - 拦截器执行通知逻辑(如前置通知 → 执行目标方法 → 后置通知)
- 最终返回目标方法结果(或修改后的结果)
四、核心总结
- 核心执行类:
AnnotationAwareAspectJAutoProxyCreator(BeanPostProcessor 实现类)是 Spring AOP 的 “核心引擎”,负责 Bean 筛选、拦截逻辑封装、代理对象生成。 - 启用开关:
@EnableAspectJAutoProxy注解的本质是向容器注册AnnotationAwareAspectJAutoProxyCreator,开启 AOP 功能(Spring Boot 2.0+ 可省略)。 - 核心设计:通过 “切点匹配 + 拦截器链” 模式,将通用逻辑与核心业务解耦,代理对象透明化了拦截过程,开发者无需关注底层代理实现。
- 执行顺序:环绕通知(前)→ 前置通知 → 目标方法 → 环绕通知(后)→ 后置通知 → 返回通知 / 异常通知。
通过以上流程,Spring AOP 实现了 “无侵入式” 的横切逻辑增强,让开发者可以专注于核心业务,同时轻松复用通用功能,是企业级开发中不可或缺的核心技术。