前言
AOP 是 Spring 核心特性之一,也是面试高频考点、日常开发必备技能。很多同学对 AOP 的理解停留在「加个注解打印日志」,但不懂底层原理,就无法真正驾驭它。
这篇文章尽量用为大家讲清,并结合 Bean 生命周期、动态代理,把 Spring AOP 的完整流程、底层原理描述清楚
一、什么是AOP?为什么要用它
1.通俗理解 AOP
AOP = Aspect Oriented Programming(面向切面编程) ,它是OOP(面向对象编程)的补充。
- OOP:以「对象」为核心,关注纵向的业务逻辑(用户登录、订单创建)
- AOP:以「切面」为核心,关注横向的通用逻辑(日志打印、权限校验、事务管理)
举个生活例子:你要给项目中 100 个 Service 方法都加上方法执行耗时统计。
- OOP 做法:每个方法手动写开始 / 结束时间 → 代码冗余、难以维护
- AOP 做法:写一个「耗时统计切面」,告诉 Spring:所有 Service 方法执行前后,自动执行统计逻辑 → 无侵入、解耦、易维护

2.AOP核心作用
一句话来描述:在不修改原有业务代码的前提下,给方法统一增强功能
典型场景:日志记录、权限校验、事务管理(Spring 事务底层就是 AOP)、接口限流、监控、异常统一处理
二、AOP相关概念
1.专业术语
要记住六个重要概念,就以给方法打印执行日志举例:
| 术语 | 英文 | 通俗解释 |
|---|---|---|
| 切面 | Aspect | 封装通用增强逻辑的类(日志、权限类) |
| 连接点 | JoinPoint | 可以被增强的方法(所有业务方法) |
| 切点 | PointCut | 真正要增强的目标方法(匹配规则,如所有 Service 方法) |
| 通知 | Advice | 增强的具体代码逻辑(方法前 / 后 / 异常打印日志) |
| 目标对象 | Target | 被增强的原始 Bean(UserService、OrderService) |
| 代理对象 | Proxy | Spring 生成的、包含增强逻辑的新对象(真正执行的对象) |
重点:切面=切点+通知、执行流程=代理对象调用目标对象+自动
2.通知的分类
在面向切面编程(AOP)中,通知(Advice)是切面(Aspect)的核心组成部分,用于定义在目标方法的特定执行点插入的横切逻辑。根据执行时机的不同,通知可以分为以下几类:
(1)前置通知(Before Advice)
在目标方法执行前触发,通常用于权限校验、日志记录等场景。例如:
java
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice() {
System.out.println("前置通知:方法执行前");
}
(2)后置通知(After Advice)
无论目标方法是否正常完成,都会在方法执行后触发。适用于资源清理等操作:
java
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice() {
System.out.println("后置通知:方法执行结束");
}
(3)返回通知(After Returning Advice)
仅在目标方法成功执行后触发,可访问返回值。例如记录结果:
java
@AfterReturning(pointcut="execution(* com.example.service.*.*(..))", returning="result")
public void afterReturningAdvice(Object result) {
System.out.println("返回通知:结果=" + result);
}
(4)异常通知(After Throwing Advice)
当目标方法抛出异常时触发,用于异常处理:
java
@AfterThrowing(pointcut="execution(* com.example.service.*.*(..))", throwing="ex")
public void afterThrowingAdvice(Exception ex) {
System.out.println("异常通知:" + ex.getMessage());
}
(5)环绕通知(Around Advice)
最强大的通知类型,可完全控制目标方法的执行流程。需手动调用ProceedingJoinPoint.proceed():
java
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知:方法执行前");
Object result = pjp.proceed();
System.out.println("环绕通知:方法执行后");
return result;
}
(6)通知的执行顺序
当多个通知作用于同一连接点时,执行顺序通常为:
- 环绕通知的前半部分
- 前置通知
- 目标方法
- 返回通知或异常通知
- 后置通知
- 环绕通知的后半部分
三、Spring AOP最简入门案例
这儿案例可以先体会一下AOP的效果,之后再细讲原理
1.依赖引入
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.业务Bean(目标对象)
java
@Service
public class UserService {
// 连接点 + 切点:要被增强的方法
public void addUser(String username) {
System.out.println("执行核心业务:添加用户 -> " + username);
}
}
3.切面类
java
@Aspect // 标记这是一个切面
@Component // 交给 Spring 管理
public class LogAspect {
// 切点:匹配 UserService 下的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void pointCut() {}
// 前置通知:目标方法执行前执行
@Before("pointCut()")
public void before() {
System.out.println("【前置通知】方法开始执行...");
}
// 后置通知:目标方法执行后执行
@After("pointCut()")
public void after() {
System.out.println("【后置通知】方法执行完毕");
}
}
4.测试
java
@SpringBootTest
public class AopTest {
@Autowired
private UserService userService;
@Test
public void test() {
userService.addUser("张三");
}
}
输出结果:
java
【前置通知】方法开始执行...
执行核心业务:添加用户 -> 张三
【后置通知】方法执行完毕
关键点:我们没有修改UserService的代码,却给他增加了日志功能-----这就是AOP的魅力
四、核心底层:Spring AOP本质=动态代理
1.一句话总结
Spring AOP 底层是动态代理,它不修改原始 Bean 代码,而是生成一个「代理对象」替代原始对象执行。
调用链:调用者 → 代理对象 → 执行通知逻辑 → 执行目标对象方法 → 执行通知逻辑
2.Spring用了两种动态代理
Spring框架会根据目标对象的类型自动选择最合适的代理模式,具体选择逻辑如下:
(1)当目标对象实现了至少一个接口时:默认使用JDK动态代理
(2)当目标对象没有实现任何接口时:使用CGLIB代理
| 代理方式 | 适用场景 | 底层实现 |
|---|---|---|
| JDK 动态代理 | 目标类实现了接口 | JDK 原生 API |
| CGLIB 代理 | 目标类没有实现接口 | 继承目标类生成子类 |
- JDK 代理:基于接口,必须有接口
- CGLIB 代理:基于继承,生成子类作为代理
3.结合上面的案例来理解代理对象
原始 Bean:UserService(只有核心业务)
代理对象:UserServiceProxy(核心业务 + 日志 / 权限 / 事务增强,注意这个是Spring自动生成的,因此代码里面没有显示)
你在代码中 @Autowired 注入的,不是原始 Bean,而是代理对象 !
五、Spring AOP完整执行流程
阶段 1:Spring 容器启动,扫描并创建原始 Bean
-
Spring 扫描
@Service/@Component,创建原始 Bean 实例(UserService) -
原始 Bean 只是普通对象,没有任何增强功能
阶段 2:AOP 核心步骤 ------ 自动生成代理对象(关键)
Spring 在 Bean 初始化完成后,执行 AOP 代理创建:
-
检查当前 Bean 是否匹配切点(PointCut)
-
匹配 → 生成代理对象,替换原始 Bean
-
不匹配 → 使用原始 Bean
-
代理对象存入 Spring 容器
阶段 3:方法调用执行
-
代码中
@Autowired注入的是代理对象 -
调用方法时,代理对象先执行通知逻辑
-
再通过反射调用原始 Bean 的目标方法
-
最后执行剩余通知