【演员突出AI助手】 今天我们来聊一聊 Spring AOP—— 一个让无数 Java 开发者又爱又恨的核心知识点。作为 演员突出AI助手,我将从痛点切入,带你系统掌握 AOP 的核心概念、底层原理和高频面试要点。
一、为什么需要 AOP?从痛点说起

假设你正在开发一个电商系统,订单、支付、用户等模块都需要记录操作日志。传统做法是在每个业务方法里手动插入日志代码:
public void createOrder(Order order) {log.info("开始创建订单,参数: {}", order); // 核心业务逻辑 orderService.save(order); log.info("订单创建成功"); } public void cancelOrder(Long id) { log.info("开始取消订单,订单ID: {}", id); // 核心业务逻辑 orderService.cancel(id); log.info("订单取消成功"); }
这种方式存在三个致命问题:
代码重复:日志、事务、权限校验等逻辑在成百上千个方法中反复出现
耦合度高:横切逻辑与核心业务代码纠缠在一起,任何一个修改都可能波及业务
维护困难:新增一个需要日志的功能,就要到处找地方插入代码,极易遗漏
为了解决这些问题,AOP(Aspect Oriented Programming,面向切面编程)应运而生-1。它的核心思想是:把那些影响多个类的公共行为封装成独立模块(切面),再通过动态代理技术“织入”到业务方法中,从而彻底解放业务代码-38。
二、核心概念:理解 AOP 的五大关键词
AOP 有五组核心概念,理解它们就等于掌握了 AOP 的“行话”-1-38。
1. 连接点(JoinPoint)
定义:程序中可以被 AOP 拦截的“点位”,在 Spring AOP 中特指方法的执行-1。
通俗理解:连接点就像商场里所有可能被安检的入口——每个入口理论上都可以拦截,但最终是否拦截由规则决定。
2. 切点(Pointcut)
定义:通过表达式定义的一组匹配规则,用来筛选哪些连接点需要被增强-1。
通俗理解:切点就像安检规则——“只检查背双肩包的顾客”。它从所有可能被拦截的点位中,筛选出真正需要增强的那部分。
3. 通知(Advice)
定义:在切点匹配到的连接点上执行的增强逻辑。Spring AOP 提供五种通知类型-1:
| 通知类型 | 注解 | 执行时机 |
|---|---|---|
| 前置通知 | @Before | 目标方法执行前 |
| 后置通知 | @After | 目标方法执行后(无论是否异常) |
| 返回通知 | @AfterReturning | 目标方法正常返回后 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常时 |
| 环绕通知 | @Around | 完全包裹目标方法,可控制执行流程 |
4. 切面(Aspect)
定义:切点 + 通知的封装体,用 @Aspect 注解标识-1。
通俗理解:切面 = 规则(切点)+ 动作(通知)。就像安检流程:规则是“检查谁”,动作是“怎么检查”。
5. 织入(Weaving)
定义:把切面逻辑应用到目标对象,生成代理对象的过程-1。
三、代理实现方式:JDK 动态代理 vs CGLIB
理解了概念之后,一个更关键的问题是:Spring AOP 在底层到底是怎么实现的?
答案是——动态代理。Spring AOP 本质上是代理模式的一种应用,通过创建目标对象的代理对象,在代理对象上织入增强逻辑-21。Spring 提供了两种动态代理实现方式-12-11。
JDK 动态代理
实现原理:基于 Java 反射机制,要求目标类必须实现至少一个接口。通过
Proxy.newProxyInstance创建实现了接口的代理对象,方法调用时回调InvocationHandler.invoke方法-22-11优点:性能较好,无需引入第三方库
缺点:必须实现接口,灵活性受限
适用场景:目标类有接口实现,且对性能有一定要求
CGLIB 动态代理
实现原理:通过字节码技术生成目标类的子类,重写目标方法并在方法调用前后插入切面逻辑-11
优点:不要求实现接口,可以代理普通类
缺点:性能略逊于 JDK(因为需要生成子类),无法代理 final 类或 final 方法
适用场景:目标类没有实现接口,或需要代理类级别的方法
Spring 如何选择?
Spring Framework(非 Boot) :默认策略——目标类有接口就选 JDK,没有就选 CGLIB-
Spring Boot 2.x 及以上:默认使用 CGLIB 代理(因为
spring-boot-starter-aop默认proxyTargetClass=true)
如果想强制指定代理方式,可以通过 @EnableAspectJAutoProxy(proxyTargetClass=true) 配置-22。
四、实战示例:一个完整的日志切面
下面通过一个完整的代码示例,让你直观感受 AOP 的魅力。
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect // 标记这是一个切面类 @Component // 交给 Spring 容器管理 public class LoggingAspect { private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class); // 定义切点:匹配 service 包下所有类的所有方法 @Pointcut("execution( com.example.service...(..))") public void servicePointcut() {} // 环绕通知:计算方法执行耗时 @Around("servicePointcut()") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); String methodName = joinPoint.getSignature().toShortString(); log.info("开始执行方法: {}", methodName); try { Object result = joinPoint.proceed(); // 关键步骤:执行原方法 long duration = System.currentTimeMillis() - start; log.info("方法 {} 执行完成,耗时 {}ms,返回结果: {}", methodName, duration, result); return result; } catch (Throwable e) { long duration = System.currentTimeMillis() - start; log.error("方法 {} 执行失败,耗时 {}ms,异常: {}", methodName, duration, e.getMessage(), e); throw e; // 重新抛出异常,不吞掉 } } // 前置通知:仅在方法执行前做简单记录 @Before("execution( com.example.controller...(..))") public void logBefore(JoinPoint joinPoint) { log.info("即将调用方法: {},参数: {}", joinPoint.getSignature().getName(), joinPoint.getArgs()); } }
关键步骤说明:
@Aspect+@Component让 Spring 识别并管理这个切面类@Pointcut定义可复用的匹配规则,避免到处重复写表达式joinPoint.proceed()是环绕通知的核心——必须手动调用,否则原业务方法永远不会执行-1代理对象调用方法时,自动触发通知逻辑,原业务代码完全无感知
五、底层原理:代理模式如何支撑 AOP?
Spring AOP 的底层实现本质上依赖于代理模式。代理模式通过引入代理对象作为目标对象的中间层,实现对目标对象访问的控制与增强-21。
两种代理的底层机制:
| JDK 动态代理 | CGLIB 动态代理 | |
|---|---|---|
| 核心技术 | 反射(java.lang.reflect.Proxy + InvocationHandler) | 字节码技术(ASM 库) |
| 代理关系 | 代理类实现目标接口 | 代理类继承目标类 |
| 执行流程 | 调用代理方法 → invoke() → 反射调用原方法 + 增强逻辑 | 调用子类重写方法 → 增强逻辑 → super.原方法() |
| 关键限制 | 目标类必须有接口 | 目标类不能是 final |
代理对象的创建流程:
Spring 容器初始化时,识别被
@Aspect标记的切面类通过
@EnableAspectJAutoProxy开启 AOP 自动代理功能-11Spring 根据目标类的特征,选择 JDK 或 CGLIB 创建代理工厂
代理工厂创建代理对象,替代原 Bean 注册到容器中
客户端调用方法时,实际执行的是代理对象的增强方法-11
六、高频面试题与参考答案
面试题 1:Spring AOP 的实现原理是什么?
参考答案:
Spring AOP 基于动态代理实现。当目标类实现接口时,使用 JDK 动态代理(基于反射,通过 Proxy 和 InvocationHandler 创建接口代理);当目标类无接口或强制指定时,使用 CGLIB 动态代理(通过字节码技术生成目标类的子类代理)。Spring 在容器初始化时,为符合条件的 Bean 创建代理对象,将切面逻辑织入代理方法中,从而实现对目标方法的增强-39。
面试题 2:JDK 动态代理和 CGLIB 有什么区别?
参考答案:
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现方式 | 基于反射,实现接口 | 基于字节码,继承目标类 |
| 接口要求 | 目标类必须实现接口 | 无接口要求 |
| 代理限制 | 只能代理接口方法 | 无法代理 final 类/方法 |
| 性能 | 代理生成快,执行稍慢 | 代理生成慢,执行更快 |
| 依赖 | JDK 原生 | 需引入 cglib 库 |
Spring Boot 2.x 后默认使用 CGLIB-39。
面试题 3:@Around 通知和其他通知的区别是什么?
参考答案:
@Around 是功能最强的通知类型,可以完全控制目标方法的执行流程。区别如下:
@Before/@After:无法获取/修改方法返回值,无法阻止方法执行
@AfterReturning:只能获取返回值,无法修改
@AfterThrowing:只能处理异常
@Around:可控制是否执行目标方法、修改参数、修改返回值、统一异常处理-28
注意:@Around 必须手动调用 proceed() 才能执行原方法,且返回值类型必须为 Object-1。
面试题 4:Spring AOP 和 AspectJ 有什么区别?
参考答案:
Spring AOP:Spring 自研实现,基于动态代理,仅支持方法级别的连接点,在运行时织入,更加轻量级
AspectJ:独立的 AOP 框架,功能更强大,支持字段、构造函数等更多连接点类型,可在编译时、类加载时、运行时织入
Spring AOP 借用了 AspectJ 的注解语法(如 @Aspect、@Before),但底层实现完全不同-。
面试题 5:AOP 不生效的常见原因有哪些?
参考答案:
目标类没有被 Spring 容器管理(未加 @Service/@Component 等注解)
切面类没有被 Spring 扫描(未加 @Component 或在启动类包外)
目标方法是 private 或 final 的(CGLIB 无法代理)
同一个类内部方法调用(this.method()),绕过了代理对象
切点表达式写错了,没有匹配到任何方法-28
七、总结
本文围绕 Spring AOP 的核心知识点展开,总结如下:
| 学习维度 | 核心要点 |
|---|---|
| 核心概念 | 切面、连接点、切点、通知、织入——五词记住 AOP |
| 实现方式 | JDK 动态代理(基于反射+接口)vs CGLIB(基于字节码+继承) |
| 五种通知 | @Before、@After、@AfterReturning、@AfterThrowing、@Around |
| 底层原理 | 代理模式 + 反射/字节码,运行时生成代理对象并织入增强逻辑 |
| 面试重点 | 实现原理、两种代理区别、@Around 特殊之处、不生效原因 |
学习建议:
先动手跑通一个简单的 @Around 日志切面,感受 AOP 的“无侵入”特性
理解代理模式是 AOP 的底层思想,JDK 和 CGLIB 只是具体实现
注意面试考点:AOP 不生效的场景是高频坑点,面试官最爱追问
下篇预告:深入剖析 AOP 代理创建源码,带你从 @EnableAspectJAutoProxy 一路追到 JdkDynamicAopProxy 和 CglibAopProxy 的底层实现。敬请期待!
