《Java 100 天进阶之路》第75篇:Spring AOP原理(2026版)
2026/8/18 6:33:59 网站建设 项目流程

第75篇:Spring AOP原理(2026精版)

📌系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第74篇:Bean生命周期 |
➡️ 下一篇:第76篇:Spring事务(待发布

🗺️ 本文阅读地图(3 分钟速览)

第74篇搞定了Bean生命周期,本篇深入Spring AOP原理。AOP是Spring框架的另一大核心,@Transactional@Async、权限校验、日志记录等功能的底层都是AOP。理解了AOP,就理解了Spring的"增强"能力从何而来:

模块核心问题一句话回答
AOP是什么面向切面编程解决什么问题?把日志、事务等横切关注点从业务代码中抽离出来,集中管理
核心概念切面、切点、通知、连接点是什么?切面是功能模块,切点定义"在哪里"执行,通知定义"做什么"
代理机制AOP底层怎么实现的?动态代理——JDK动态代理(基于接口)或CGLIB(基于继承)
JDK vs CGLIB两种代理怎么选?目标类有接口用JDK,无接口用CGLIB;Spring Boot 2.x+默认CGLIB
执行顺序多个通知的执行顺序?@Around(前) →@Before→ 目标方法 →@Around(后) →@After@AfterReturning/@AfterThrowing
AOP失效场景为什么@Transactional有时不生效?自调用、非public方法、异常被吞掉等
面试最爱问高频考点有哪些?见文末 面试小节

一、核心知识点

1. AOP是什么?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在将横切关注点(cross-cutting concerns)与核心业务逻辑分离。横切关注点是指那些跨越多个业务模块的通用功能——日志记录、事务管理、权限控制、性能监控等。AOP通过代理模式实现了对这些功能的集中管理,使核心业务逻辑与通用功能解耦。

💡OOP vs AOP:OOP按纵向维度(继承、封装、多态)组织代码,适合描述"是什么";AOP按横向维度(切面)组织代码,适合描述"在什么时机做什么事"。两者互补,不是替代关系。

2. AOP解决了什么问题?

问题传统方式AOP方式
代码重复每个业务方法都要写日志、事务代码切面集中定义,一处修改处处生效
耦合度高业务代码混杂着非业务逻辑业务类只关注核心逻辑,干净纯粹
难以维护改一个日志格式要改几百个文件只改切面类即可
难以扩展新增功能要改所有相关类新增切面类,无侵入式扩展

二、通俗讲解(1分钟开心学)

把AOP想象成"餐厅的中央厨房"

  • 核心业务(OOP):餐厅的"菜谱"——宫保鸡丁怎么做、水煮鱼怎么做,每道菜有自己独特的做法(纵向)。
  • 横切关注点(AOP):餐厅的"后勤管理"——所有菜都要洗菜(前置处理)、装盘(后置处理)、记录每道菜耗时(环绕处理)。这些事跟菜本身的做法无关,但每道菜都要做。
  • 传统方式:每个厨师做菜时都要自己洗菜、装盘、记时间——代码重复,改起来麻烦。
  • AOP方式:专门配一个后勤团队(切面)统一负责洗菜、装盘、记时间。厨师只管炒菜。哪天要换装盘方式,只改后勤团队,所有菜自动生效。

AOP的核心比喻

  • 切面(Aspect)= 后勤团队(日志团队、事务团队、权限团队)
  • 切点(Pointcut)= 规则"所有热菜都要洗菜"(匹配哪些方法)
  • 通知(Advice)= 具体动作"洗菜"(在切点执行的代码)
  • 连接点(Join Point)= 每一次"洗菜"的具体时机(方法执行的某个时刻)
  • 织入(Weaving)= 把后勤团队"嵌入"到厨师做菜流程中的过程

三、AOP核心概念详解

3.1 六大核心概念

Spring AOP建立在几个关键抽象之上:

概念英文通俗理解示例
连接点Join Point程序执行过程中可以插入切面逻辑的位置(如方法调用、异常抛出)任何Service方法调用
切点Pointcut通过表达式匹配一组连接点,决定"在哪里"执行execution(* com.example.service.*.*(..))
通知Advice在特定连接点执行的具体动作,决定"做什么"@Before记录日志、@Around开启事务
切面Aspect封装横切关注点的模块,包含通知和切点@Aspect标注的LogAspect
目标对象Target Object被代理的原始业务对象UserServiceImpl实例
织入Weaving将切面代码与目标对象关联的过程Spring在运行时通过动态代理完成

💡Spring AOP的限制:Spring AOP仅支持方法级别的连接点——只能在方法调用时插入切面逻辑,不支持字段访问、构造器调用等更细粒度的连接点。如果需要更强大的AOP能力,需要配合AspectJ使用。

3.2 五种通知类型

Spring AOP提供了五种通知类型,在不同的时机插入横切逻辑:

注解类型执行时机典型场景
@Before前置通知目标方法执行前参数校验、权限检查
@After后置通知目标方法执行后(无论是否异常,类似finally资源清理、释放连接
@AfterReturning返回后通知目标方法正常返回后记录返回值、缓存更新
@AfterThrowing异常通知目标方法抛出异常后异常监控、告警
@Around环绕通知包裹目标方法,控制其执行流程事务管理、性能监控、日志记录

💡@Around最强大:它可以完全控制目标方法的执行——在方法执行前后插入逻辑,甚至可以决定是否执行目标方法。@Transactional的底层实现就是@Around通知。

四、AOP底层原理:动态代理

4.1 代理模式核心思想

Spring AOP的实现本质上依赖于代理模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强。Spring AOP在运行时通过动态代理将切面代码织入目标对象。

💡核心理解:AOP的底层就是动态代理。容器中放的不是原始对象,而是代理对象。你调用userService.saveUser()时,实际调用的是代理对象的方法——代理对象先执行切面逻辑(事务开启、日志记录),再调用原始对象的方法。

4.2 JDK动态代理 vs CGLIB

Spring AOP使用两种方式创建动态代理:

对比维度JDK动态代理CGLIB动态代理
实现方式基于接口生成代理类通过继承目标类生成子类
使用条件目标类实现了至少一个接口目标类没有实现接口(或强制指定)
核心类java.lang.reflect.Proxy+InvocationHandlernet.sf.cglib.proxy.Enhancer
方法调用通过反射调用invoke()通过方法拦截器调用,性能更高
限制只能代理接口中定义的方法final类无法代理,final/private方法无法增强

JDK动态代理实现示例

// 1. 定义接口publicinterfaceUserService{voidsaveUser(Useruser);}// 2. 实现类publicclassUserServiceImplimplementsUserService{publicvoidsaveUser(Useruser){// 业务逻辑}}// 3. 实现InvocationHandlerpublicclassLogInvocationHandlerimplementsInvocationHandler{privateObjecttarget;publicLogInvocationHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("【前置】方法:"+method.getName()+" 开始执行");Objectresult=method.invoke(target,args);System.out.println("【后置】方法:"+method.getName()+" 执行完成");returnresult;}}// 4. 创建代理UserServicetarget=newUserServiceImpl();InvocationHandlerhandler=newLogInvocationHandler(target);UserServiceproxy=(UserService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);proxy.saveUser(newUser());
4.3 Spring如何选择代理方式?

Spring的代理选择策略:

场景代理方式
目标类实现了接口JDK动态代理(默认)
目标类未实现接口CGLIB动态代理
强制使用CGLIB设置@EnableAspectJAutoProxy(proxyTargetClass = true)

💡Spring Boot的默认行为:Spring Boot从2.0开始,默认将spring.aop.proxy-target-class设置为true,即优先使用CGLIB代理。这意味着即使你的类实现了接口,Spring Boot也会用CGLIB来代理,这样可以代理类中所有方法(而不仅仅是接口中定义的方法)。

强制使用CGLIB的配置方式

@Configuration@EnableAspectJAutoProxy(proxyTargetClass=true)// 强制CGLIBpublicclassAppConfig{}
# application.ymlspring:aop:proxy-target-class:true# Spring Boot 2.x+ 默认就是true
4.4 Spring AOP vs AspectJ

很多开发者容易混淆Spring AOP和AspectJ,两者有本质区别:

对比维度Spring AOPAspectJ
织入时机运行时动态代理编译时类加载时织入
实现方式生成代理对象直接修改字节码
连接点支持方法调用字段访问、构造器、静态代码块等
性能略低(代理调用链)更高(直接执行增强后的字节码)
使用复杂度简单,与Spring无缝集成需要AspectJ编译器(ajc)
适用场景轻量级、Spring Bean的方法增强复杂AOP需求、非Spring管理的对象

💡一句话总结Spring AOP是"运行时套壳子",AspectJ是"编译时改代码"。大多数场景下Spring AOP足够用,只有在需要代理非Spring Bean或需要字段级别拦截时,才需要AspectJ。

五、AOP代理创建流程(源码级)

5.1 三大核心步骤

Spring AOP代理对象的创建分为三个核心步骤:

5.2 源码核心类解析

Spring AOP的代理创建由AbstractAutoProxyCreator驱动,它是BeanPostProcessor的一个实现,在Bean生命周期中发挥作用。

核心类继承关系

BeanPostProcessor(Bean生命周期扩展点) ↑ 实现 AbstractAutoProxyCreator(自动代理创建器基类) ↑ 继承 AbstractAdvisorAutoProxyCreator(增加Advisor处理能力) ↑ 继承 AnnotationAwareAspectJAutoProxyCreator(@AspectJ注解驱动,实际使用的实现类)

核心方法postProcessBeforeInstantiation

@OverridepublicObjectpostProcessBeforeInstantiation(Class<?>beanClass,StringbeanName){// 1. 检查是否应该跳过if(isInfrastructureClass(beanClass)||shouldSkip(beanClass,beanName)){this.advisedBeans.put(cacheKey,Boolean.FALSE);returnnull;}// 2. 获取自定义TargetSourceTargetSourcetargetSource=getCustomTargetSource(beanClass,beanName);if(targetSource!=null){// 3. 获取适用于该Bean的增强器Object[]specificInterceptors=getAdvicesAndAdvisorsForBean(beanClass,beanName,targetSource);// 4. 创建代理对象returncreateProxy(beanClass,beanName,specificInterceptors,targetSource);}returnnull;}

代理创建流程

  1. BeanPostProcessor机制:在Bean实例化之前,AbstractAutoProxyCreator拦截Bean的创建过程
  2. 解析切面定义:扫描所有@Aspect切面类,构建Advisor集合
  3. 匹配增强器:通过切点表达式判断当前Bean是否需要被代理
  4. 创建ProxyFactory:将目标对象、增强器等信息注入代理工厂
  5. 选择代理策略:根据目标类是否实现接口,选择JDK或CGLIB
  6. 生成代理对象:通过ProxyFactory创建代理对象,替换容器中的原始Bean

六、AOP执行顺序

6.1 单个切面中五种通知的执行顺序

当一个切面中定义了多种通知时,执行顺序如下:

代码示例

@Aspect@ComponentpublicclassLogAspect{@Around("execution(* com.example.service.*.*(..))")publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{System.out.println("① @Around 前置:方法开始");Objectresult=pjp.proceed();// 执行目标方法System.out.println("④ @Around 后置:方法结束");returnresult;}@Before("execution(* com.example.service.*.*(..))")publicvoidbefore(){System.out.println("② @Before:前置通知");}@After("execution(* com.example.service.*.*(..))")publicvoidafter(){System.out.println("⑤ @After:后置通知(finally)");}@AfterReturning("execution(* com.example.service.*.*(..))")publicvoidafterReturning(){System.out.println("⑥ @AfterReturning:正常返回");}}

执行结果

① @Around 前置:方法开始 ② @Before:前置通知 —— 目标方法执行 —— ④ @Around 后置:方法结束 ⑤ @After:后置通知(finally) ⑥ @AfterReturning:正常返回
6.2 多个切面的执行顺序

当多个切面作用于同一个目标方法时,通过@Order注解控制执行顺序:

规则说明
@Order(数值)数值越小,优先级越高
优先级高的切面外层(先执行前置,后执行后置)
优先级低的切面内层(后执行前置,先执行后置)

多个切面嵌套执行顺序

切面A(@Order(1),外层)@Around前置 ↓ 切面B(@Order(2),内层)@Around前置 ↓ 切面B @Before ↓ 目标方法执行 ↓ 切面B @Around后置 ↓ 切面B @After ↓ 切面A @Around后置 ↓ 切面A @After

💡记忆口诀前置通知"数值小先执行",后置通知"数值大先执行"——整体像一个"栈",先进后出。

七、AOP失效场景(高频面试题)

AOP基于动态代理实现,以下场景会导致AOP失效:

7.1 自调用(最经典的失效场景)

问题代码

@ServicepublicclassUserService{@Transactional// ❌ 不会生效!publicvoidupdateUser(Useruser){// 更新用户逻辑}publicvoidupdateUserWithLog(Useruser){// 直接调用本类方法——绕过了代理对象!this.updateUser(user);// ❌ AOP失效logService.saveLog("更新用户");}}

为什么失效?

this.updateUser()调用的是原始对象的方法,而不是代理对象的方法。Spring的AOP增强是通过代理对象实现的——只有通过代理对象调用方法,切面逻辑才会执行。

解决方案

@ServicepublicclassUserService{@AutowiredprivateUserServiceself;// 注入自身(代理对象)@TransactionalpublicvoidupdateUser(Useruser){// 更新用户逻辑}publicvoidupdateUserWithLog(Useruser){self.updateUser(user);// ✅ 通过代理对象调用,事务生效}}

或者使用AopContext.currentProxy()

((UserService)AopContext.currentProxy()).updateUser(user);
7.2 其他常见失效场景
失效场景原因解决方案
非public方法代理只能增强public方法改为public
异常被try-catch吞掉通知无法捕获异常在catch中重新抛出异常
内部方法调用(自调用)绕过代理对象通过代理对象调用或拆分到不同类
方法被final修饰CGLIB无法重写final方法移除final修饰
类被final修饰CGLIB无法继承final类移除final修饰

八、避坑要点

错误/误区后果正确做法
认为AOP能代理所有方法private/final方法增强失败只对public方法使用AOP
在类内部调用自己的AOP方法切面逻辑不执行(自调用)通过代理对象调用或拆分到不同类
混淆Spring AOP和AspectJ对AOP能力预期错误Spring AOP仅支持方法级别,运行时织入
忘记加@EnableAspectJAutoProxy@Aspect不生效Spring Boot自动配置,无需手动添加
@Around中忘记调用proceed()目标方法不执行必须在@Around中调用pjp.proceed()
认为JDK代理比CGLIB快选错代理方式JDK代理有反射开销,CGLIB性能更好

九、面试高频考点

Q1:AOP的底层原理是什么?

AOP的底层是动态代理。Spring在运行时为目标Bean创建代理对象(JDK动态代理或CGLIB),将切面逻辑织入代理对象中。当调用目标方法时,实际调用的是代理对象的方法——代理对象先执行切面逻辑(通知),再调用原始对象的方法。

Q2:JDK动态代理和CGLIB有什么区别?

JDK动态代理基于接口,要求目标类实现至少一个接口,通过ProxyInvocationHandler生成代理。CGLIB基于继承,通过生成目标类的子类来实现代理,不需要接口。JDK只能代理接口中定义的方法,CGLIB可以代理类中所有非final方法。Spring Boot 2.x+默认使用CGLIB。

Q3:@Transactional为什么会失效?

最经典的是自调用失效——同一个类中,一个非事务方法调用本类的@Transactional方法,调用的是原始对象的this引用,绕过了代理对象,事务不生效。此外,private方法、final方法、异常被try-catch吞掉、未启用事务管理也会导致失效。

Q4:@Around@Before+@After有什么区别?

@Around环绕通知,可以完全控制目标方法的执行——决定是否执行、何时执行、执行前后做什么。@Before+@After只能分别在方法前后执行逻辑,无法控制方法是否执行。@Transactional的底层就是@Around通知——开启事务→执行业务方法→根据结果提交或回滚。

Q5:Spring AOP和AspectJ有什么区别?

Spring AOP是运行时通过动态代理织入,仅支持方法级别的连接点。AspectJ是编译时类加载时通过修改字节码织入,支持字段、构造器、静态代码块等更丰富的连接点。Spring AOP更简单易用,AspectJ功能更强大、性能更好。

🎤 面试官追问陷阱(加分题)

追问1:“CGLIB能代理final类吗?能代理final方法吗?”

👉都不能。CGLIB通过继承目标类生成子类来实现代理——final类不能被继承,final方法不能被重写。所以如果你的类或方法被final修饰,AOP增强会失效。Spring会fallback到JDK动态代理吗?不会——如果类没有接口且是final的,代理创建会直接失败。

追问2:“Spring Boot默认用CGLIB,那如果一个类既实现了接口,又被final修饰了,会怎样?”

👉 这是个好问题。Spring Boot默认CGLIB,但final类无法被CGLIB代理。此时Spring会自动降级到JDK动态代理——只要该类实现了至少一个接口。所以结果是:代理可以创建成功,但使用的是JDK方式。这说明Spring的代理选择策略是有"容错"机制的——CGLIB不行就换JDK。

追问3:“@Around通知中调用了pjp.proceed()两次,会怎样?”

👉 目标方法会被执行两次。这在某些场景下是故意的(比如重试机制),但大多数情况下是bug。每次proceed()都会重新执行目标方法及其嵌套的切面逻辑。所以如果你在@Around中不小心写了两次proceed(),事务可能会被开启两次、日志会记录两遍。

十、练习题

  1. 简答题:JDK动态代理和CGLIB动态代理在实现原理上有什么本质区别?各自的使用条件是什么?

  2. 代码题:编写一个@Around环绕通知,统计目标方法的执行时间,并在方法执行前后打印日志。

  3. 分析题:某项目中@Transactional注解在UserService.updateUser()方法上不生效,但OrderService.createOrder()方法上的@Transactional正常。updateUser()是被UserService内部的另一个方法调用的。请分析原因并给出解决方案。

📊 你的学习进度

  • 当前:第75篇 / 共108篇 ·进阶篇:Spring全家桶(第73~82篇)
  • ✅ 已完成:基础篇44篇 + 第45~75篇
  • 📖 正在学:第75篇
  • ⏳ 待学习:第76~108篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇

👉 下一篇文章预告

🚀下一篇:《第76篇:Spring事务》

内容简介:声明式事务@Transactional原理、事务传播行为(REQUIRED/REQUIRES_NEW/NESTED等7种)、隔离级别、事务失效场景深度剖析、编程式事务。

👉Spring全家桶专题持续推进,拿下事务管理!

📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询