Spring AOP是Spring框架的两大核心技术之一,它将横切关注点(如日志记录、事务管理、安全控制)与业务逻辑分离,显著提升了代码的模块化程度和可维护性-3。许多开发者在日常工作中频繁使用AOP却知其然不知其所以然——会用@Before和@Around注解,但被问到“JDK动态代理和CGLIB有什么区别”“为什么AOP会失效”时就答不上来了。本文将从痛点切入,由浅入深讲解Spring AOP的核心概念、实现原理与面试要点,通过代码示例和对比分析,帮你在30分钟内理清思路,建立完整的知识链路。
一、痛点切入:为什么需要AOP

先看一个典型场景:你需要在每个Service方法执行前后记录日志。在传统OOP模式下,代码会写成这样:
public void createOrder(Order order) {System.out.println("[LOG] 开始执行 createOrder,参数:" + order); // 日志代码 // 核心业务逻辑 orderDao.save(order); System.out.println("[LOG] createOrder 执行完成"); } public void updateOrder(Order order) { System.out.println("[LOG] 开始执行 updateOrder,参数:" + order); // 核心业务逻辑 orderDao.update(order); System.out.println("[LOG] updateOrder 执行完成"); }
这种做法的痛点十分明显:
代码重复:日志、事务、权限校验等公共代码在每个方法中反复出现
耦合度高:横切逻辑与核心业务代码混在一起,修改一处需要改动几十上百个地方
维护困难:新增一个Service方法时容易遗漏公共逻辑,排查问题也异常困难
复用性差:想换一种日志记录方式,几乎要重写所有方法
传统OOP擅长通过封装、继承、多态构建垂直结构的层次体系,但面对分散在多个模块中的横切关注点时却显得力不从心-12-18。AOP正是为了解决这一问题而诞生的——它将公共逻辑横向抽取出来,集中管理,运行时再织入到业务方法中。
二、核心概念讲解:AOP的四大基石
2.1 AOP定义
AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,其核心思想是将跨越多个模块的公共功能(如日志、事务、安全验证)从业务逻辑中提取出来,在程序运行时动态地将这些增强代码插入到目标方法中-12。
一个形象的类比:假设你经营一家图书馆,有几十种还书方式——前台还书、自助机还书、APP还书。你想在每个读者还书时自动附上一句“记得按时还书哦”。如果没有AOP,你得在每一种还书方法的代码里都加上这一句,几十个地方都要改。而AOP的做法是:不修改任何还书代码,只配置一条规则“在还书方法执行时自动提醒”,剩下的交给框架处理-1。
2.2 四个核心术语
有了上面的例子,四个核心概念就很好理解了-1-2:
| 术语 | 英文 | 类比(图书馆还书) | 技术含义 |
|---|---|---|---|
| 连接点 | Join Point | 所有可以加提醒的地方:前台还书、自助机还书、APP还书 | 程序执行过程中可被拦截的位置(如方法调用) |
| 切点 | Pointcut | 只关心“还书”方法,不关心借书、查书 | 通过表达式匹配一组连接点的规则 |
| 通知 | Advice | 提醒动作——“记得按时还书哦” | 切面在连接点执行的具体增强逻辑 |
| 切面 | Aspect | 切点 + 通知 = “在还书时加上提醒” | 封装横切关注点的模块 |
一句话总结:切面 = 切点 + 通知,即在哪些连接点做什么事。 -1
2.3 五种通知类型
Spring AOP支持五种通知,每种有明确的触发时机-3-2:
@Before(前置通知) :目标方法执行前触发,适用于参数校验、权限控制
@After(后置通知) :目标方法执行后触发(无论是否抛异常),适用于资源清理
@AfterReturning(返回后通知) :目标方法正常返回后触发,可访问返回值
@AfterThrowing(异常通知) :目标方法抛出异常后触发,可捕获异常信息
@Around(环绕通知) :包裹目标方法,可控制是否执行目标方法,最强大也最常用
其中@Around通知最灵活——它可以在方法执行前后都插入逻辑,甚至可以决定是否继续执行目标方法,通常用于事务控制和性能监控-3。
三、关联概念讲解:Spring AOP vs AspectJ
在学习和面试中,经常被问到“Spring AOP和AspectJ有什么区别”。这两个概念容易混淆,搞清楚它们的区别对深入理解AOP非常有帮助。
3.1 标准定义
Spring AOP:Spring框架对AOP思想的轻量级实现,基于动态代理机制,在运行时织入切面逻辑
AspectJ:一个功能完整的AOP框架,支持在编译时和类加载时织入,功能更强大
3.2 核心区别
| 对比维度 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时动态代理 | 编译时或类加载时 |
| 连接点支持 | 仅方法级别 | 方法、字段、构造器、静态代码块等 |
| 性能 | 略低(运行时生成代理) | 更高(编译时优化) |
| 第三方依赖 | 无额外依赖 | 需引入AspectJ库 |
| 使用场景 | 轻量级应用,简单切面 | 企业级复杂切面需求 |
Spring AOP对AspectJ注解的支持是通过@EnableAspectJAutoProxy开启的,底层仍然是基于代理实现-2。
一句话总结:Spring AOP是轻量级的运行时代理实现,AspectJ是功能更全面的编译时织入框架,Spring借用了AspectJ的注解语法,但底层用的是自己的代理机制。 -2
四、概念关系与区别总结
梳理清楚AOP核心概念之间的关系:
切面(Aspect)是思想:模块化横切关注点的设计理念
通知(Advice)是做什么:具体要执行的增强逻辑(何时做)
切点(Pointcut)是在哪做:通过表达式匹配连接点的规则
连接点(Join Point)是候选位置:程序执行中所有可能被拦截的点
织入(Weaving)是咋做:将切面应用到目标对象并创建代理对象的过程-3-12
一句话记忆公式:切面(切点+通知)通过织入,在连接点执行增强。
五、代码示例:从零实现一个日志切面
我们用一个完整的示例来串联上面的概念。场景:记录Service层所有方法的执行耗时。
5.1 启用AOP功能
@SpringBootApplication @EnableAspectJAutoProxy // 开启AspectJ代理支持 public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
5.2 定义切面类
@Aspect @Component public class PerformanceAspect { // 切点:匹配 com.example.service 包下所有类的所有方法 @Pointcut("execution( com.example.service..(..))") public void serviceMethod() {} // 环绕通知:记录方法执行耗时 @Around("serviceMethod()") public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); // 执行目标方法 Object result = joinPoint.proceed(); long end = System.currentTimeMillis(); String methodName = joinPoint.getSignature().toShortString(); System.out.println(methodName + " 耗时: " + (end - start) + "ms"); return result; } }
关键步骤说明:
@Aspect标注切面类,@Component交给Spring容器管理@Pointcut定义切点表达式,确定哪些方法会被拦截@Around环绕通知中调用proceed()执行目标方法-2
5.3 业务代码(完全无侵入)
@Service public class UserService { public String getUserById(int id) { // 纯业务逻辑,没有任何日志代码 return "User_" + id; } }
运行后,每次调用UserService.getUserById()会自动输出耗时日志——业务代码零侵入,真正实现了关注点分离。 -12-42
六、底层原理:动态代理机制
6.1 核心原理
Spring AOP的底层实现依赖动态代理技术,核心机制是通过代理对象拦截目标方法的调用,并在调用前后插入切面逻辑-3。当客户端通过代理对象调用目标方法时,代理对象会拦截这个调用,根据切面配置找到对应的通知,按照责任链模式依次执行增强逻辑-21。
6.2 JDK动态代理 vs CGLIB
Spring AOP支持两种动态代理方式,具体选择取决于目标类的特征-2-21-22:
| 对比维度 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 实现原理 | 基于反射,生成实现接口的代理类 | 基于字节码技术,生成目标类的子类 |
| 必要条件 | 目标类必须实现至少一个接口 | 目标类无需接口,但不能是final类 |
| 代理方式 | 接口代理 | 类代理(继承) |
| 生成代理速度 | 较快 | 较慢 |
| 执行方法速度 | 较慢 | 较快(约快10倍) |
| 依赖 | JDK原生支持 | 需引入CGLIB库 |
面试高频对比:CGLIB创建的代理对象执行方法的速度比JDK快约10倍,但创建代理对象的时间比JDK多约8倍。对于单例代理对象(如Spring Bean),CGLIB是更优选择;对于频繁创建代理对象的场景,JDK代理更合适-。
6.3 Spring的代理选择逻辑
Spring Framework(传统Spring) :默认使用JDK动态代理,当目标类未实现接口时自动切换到CGLIB
Spring Boot:默认使用CGLIB作为代理策略-
6.4 底层技术支撑
AOP功能的实现依赖于以下基础技术:
反射(Reflection) :JDK动态代理的核心,通过
InvocationHandler.invoke()在运行时调用目标方法字节码操作:CGLIB基于ASM字节码库动态生成子类
责任链模式:Spring AOP通过
ReflectiveMethodInvocation管理多个通知的执行顺序-2Spring容器:代理对象的生成和注入由IoC容器管理,只有容器中的Bean才能被AOP代理-32
七、高频面试题与参考答案
面试题1:Spring AOP的实现原理是什么?JDK动态代理和CGLIB有什么区别?
参考答案:
Spring AOP基于动态代理实现,在运行时为目标对象生成代理对象,通过代理拦截方法调用并在调用前后插入切面逻辑。两种代理方式的主要区别:
JDK动态代理:基于反射,要求目标类实现接口,生成实现相同接口的代理类,创建速度快但执行略慢
CGLIB代理:基于字节码技术生成目标类的子类,无需接口支持,无法代理final类和方法,创建较慢但执行更快(约快10倍)
Spring Boot默认使用CGLIB,传统Spring默认使用JDK动态代理。-31-
面试题2:Spring AOP在什么场景下会失效?如何解决?
参考答案:
失效场景及解决方案:
内部方法自调用:类内部用
this.method()直接调用,未经过代理对象。解决:注入自身代理对象或通过ApplicationContext获取非public方法:代理无法拦截private/final/static方法。解决:确保目标方法为public
对象未被Spring管理:手动
new创建的对象。解决:交给Spring容器管理切点表达式匹配错误。解决:用测试类验证表达式
根本原因在于调用未经过代理对象。-34
面试题3:Spring AOP中通知的执行顺序是怎样的?
参考答案:
正常情况下(无异常):@Around(前半部分) → @Before → 目标方法 → @AfterReturning → @After → @Around(后半部分)
异常情况下:@Around(前半部分) → @Before → 目标方法(抛异常) → @AfterThrowing → @After
Spring AOP通过责任链模式(ReflectiveMethodInvocation)管理通知的执行顺序。-2
面试题4:如何让AOP拦截同一个类中自调用的方法?
参考答案:
由于自调用走的是this引用而非代理对象,解决方法有三种:
将自身注入到当前类,通过代理对象调用
通过
ApplicationContext.getBean()获取代理对象将需要增强的方法提取到独立的Service中
推荐第三种,因为它符合单一职责原则且最清晰。-32
八、结尾总结
核心知识点回顾
| 知识点 | 要点 |
|---|---|
| 为什么需要AOP | 解决横切关注点代码重复、耦合高的问题 |
| 四个核心概念 | 切面=切点+通知,在连接点执行增强 |
| 五种通知类型 | @Before、@After、@AfterReturning、@AfterThrowing、@Around |
| 两种代理机制 | JDK动态代理(接口)vs CGLIB(子类) |
| 失效场景 | 内部自调用、非public方法、非容器管理对象 |
| 与AspectJ关系 | Spring AOP轻量级运行时代理,AspectJ功能更全面 |
易错点提醒
自调用陷阱:同一个Bean内部调用被AOP增强的方法,切面不会生效——这是日常开发中最容易踩的坑
代理方式选择:Spring Boot默认CGLIB,但
final类和方法无法被CGLIB代理环绕通知必须
proceed():忘记调用joinPoint.proceed()会导致目标方法不执行
进阶预告
下一篇文章将深入讲解AOP在声明式事务管理中的应用,包括事务传播行为、隔离级别配置以及多数据源事务的常见坑点,敬请期待!
📌 本文核心记忆卡片:AOP=代理+切点+通知;JDK用接口,CGLIB建子类;自调用是最大坑点。

扫一扫微信交流