当前位置: 首页 > news >正文

Spring AOP 底层到底怎么跑的,我翻了一圈源码终于搞明白了

上一篇博客我整理了 AOP 的八个概念,算是知道了"是什么"。但心里一直有个疑问:Spring 到底是怎么做到的?我在类上加个@Aspect,Spring 就能自动帮我拦截方法了?这背后发生了什么?

这篇就把我查到的东西整理一下,尽量用自己能理解的话说。

代理对象是怎么来的

上一篇说过,AOP 的核心是代理。你调的UserService其实不是真正的UserService,而是 Spring 给你生成的一个代理对象。那这个代理对象是什么时候、怎么生成的?

答案是 Spring 容器启动的时候。

Spring 里有一个东西叫BeanPostProcessor,翻译过来就是 Bean 的后处理器。它的工作是:在每个 Bean 创建完成之后检查一下,看看这个 Bean 有没有被某个切面匹配到。如果有,就不把原始对象放进容器了,而是给它生成一个代理对象放进去。

所以当你在代码里写@Autowired UserService userService的时候,Spring 给你的就已经是代理了。你从头到尾都没碰过原始的UserService

那代理对象是怎么生成的?这就涉及到两种技术了。

两种代理方式:JDK 动态代理和 CGLIB

Spring 生成代理对象有两种方式,取决于目标对象长什么样。

第一种:JDK 动态代理。这是 Java 自带的,用的java.lang.reflect.Proxy这个类。它能在程序运行的时候动态生成一个实现了目标接口的代理类。所有方法调用都会被转发到一个InvocationHandlerinvoke方法里,你就可以在这里面加通知逻辑。

但它有个硬限制:目标对象必须实现了接口。因为 JDK 动态代理的原理是实现接口,不是继承类。没接口就没法用。

第二种:CGLIB 字节码增强。CGLIB 用的是一个叫 ASM 的底层框架,它能在运行的时候动态生成目标类的子类。代理对象是目标类的儿子,重写了父亲的方法。调用的时候通过MethodProxy触发逻辑。

因为是基于继承,所以不需要接口,什么类都能代理。但也有个限制:final修饰的类和方法没法被继承和重写,所以 CGLIB 对final无能为力。

Spring 怎么选?如果目标对象实现了接口,默认用 JDK 动态代理。没实现接口,就自动降级用 CGLIB。不过 Spring Boot 2.x 之后把默认改成了 CGLIB,因为 JDK 代理在某些场景下会有坑,统一用 CGLIB 更省事。

我自己写代码的时候一般不去管它用哪种,让 Spring 自己决定就行。知道有这么回事,主要是为了遇到代理失效的时候能排查原因。比如你给一个final方法加了切面发现没生效,这时候你就知道:哦,CGLIB 没法重写final方法。

Spring AOP 和 AspectJ 的区别:运行期织入

查资料的时候我发现 AOP 的实现不止 Spring 一种,还有一个叫 AspectJ 的东西,功能更强。它俩的区别主要在"织入"的时机上。

AspectJ 是编译期或类加载期织入。它有自己的编译器(叫ajc),在代码编译成.class文件的时候,或者类加载到 JVM 的时候,直接把切面代码改写到目标类的字节码里。改完之后目标类的.class文件就已经包含切面逻辑了。

这种方式性能好,因为运行时就是一段普通的代码,没有额外的代理开销。但代价是你得换编译器,或者配置特殊的 ClassLoader,对项目有侵入。

Spring AOP 是运行期织入。目标类的源代码和字节码一个字都不动。Spring 在程序跑起来之后,在内存中动态生成一个代理对象。你调的是代理,代理再去调真正的目标对象。

性能上比 AspectJ 稍微差一点点,因为多了一层代理转发。但好处是对项目零侵入,不需要换编译器,不需要改构建流程,只要用了 Spring 容器就能直接用。这也是为什么大部分 Spring 项目都用 Spring AOP 而不是 AspectJ。

不过如果你需要拦截字段赋值、构造函数这些 Spring AOP 搞不定的场景,那就只能上 AspectJ 了。

拦截器链:多个切面怎么协作

实际项目里,一个方法上可能叠了好几层切面。比如一个接口既要记日志,又要验权限,还要管事务。这三个切面都匹配到了同一个方法,那它们按什么顺序执行?

Spring 底层用的是责任链模式。

具体过程是这样的:所有的通知(不管是@Before@After还是@Around),Spring 都会把它们统一包装成MethodInterceptor接口。然后把匹配到这个方法的所有拦截器排成一条链,放在一个MethodInvocation对象里。

方法被调用的时候,不是直接去执行目标方法,而是从链的第一个拦截器开始走。每个拦截器做完自己的前置逻辑之后,调用invocation.proceed()把控制权交给下一个拦截器。等所有拦截器都走完了,最后才通过反射调用真正的目标方法。

目标方法执行完之后,调用链又逆序往回走,依次触发各个拦截器的后置逻辑。

画出来大概是这样(假设日志切面在外层,事务切面在内层):

调用代理方法 → 日志拦截器:记录开始时间 → 事务拦截器:开启事务 → 执行真正的目标方法 ← 事务拦截器:提交事务 ← 日志拦截器:记录耗时

每个拦截器只关心自己的事,互相不知道对方的存在。这就是责任链的好处:你加一个新切面,不需要改已有的任何一个切面。

Around 通知的 proceed() 到底在干嘛

五种通知里,环绕通知(@Around)是最灵活的,也是最容易写错的。它的核心就是那个proceed()方法。

proceed()做的事情是:把控制权交给链中的下一个拦截器(或者最终的目标方法)。如果你不调proceed(),后面的拦截器和目标方法都不会执行。

这个特性可以用来做很多事情。比如权限校验:如果用户没权限,直接不调proceed(),方法就被拦住了。但这也意味着如果你忘了调proceed(),业务方法永远不会执行,而且不会报任何错,就是静默失败了。这个坑我踩过一次,排查了半天。

@Around("serviceLayer()")publicObjectcheckPermission(ProceedingJoinPointjoinPoint)throwsThrowable{if(!hasPermission()){thrownewRuntimeException("没有权限");}// 如果这里忘了写 proceed(),目标方法就永远不会执行returnjoinPoint.proceed();}

小结一下

Spring AOP 底层其实就做了三件事:

第一,容器启动时扫描所有 Bean,看它们有没有被切面匹配到,匹配到的就生成代理对象替换掉原始对象。

第二,生成代理的方式有两种:有接口用 JDK 动态代理,没接口用 CGLIB。目标类的字节码一个字都不动。

第三,方法被调用的时候,Spring 把匹配到的所有切面包装成拦截器链,按顺序依次执行,最后才调用目标方法。

概念层面看着很抽象,但底层拆开看其实都是 Java 的基本功:反射、动态代理、责任链模式。搞清楚这几个东西,AOP 的原理就没那么玄了。

http://www.jsqmd.com/news/1045587/

相关文章:

  • 2026湘潭本地人必选防水补漏检测维修公司靠谱服务商TOP5推荐:房屋渗漏水检测维修/卫生间/厨房/天花板/阳台/外墙渗漏水检测补漏维修-暗管漏水检测专业仪器精准定位漏水点 - 即刻修防水
  • 2026年成都GEO优化机构怎么选?从核心逻辑到机构测评全指南
  • 2026珠海2026正规漏水检测维修公司精选口碑榜TOP5权威推荐-精准定位检测漏水点-专业防水补漏堵漏维修、卫生间/厨房/屋顶/天沟/地下室/阳台防水漏水检测维修 - 安佳防水
  • Pixelle-Video:零基础AI视频生成完全指南,3分钟创作专业短视频
  • Mamba在视觉识别任务中真的必要吗?
  • DDPG 算法直觉 (without code)
  • 2026湛江漏水检测维修精选优质服务商TOP5推荐!卫生间漏水/厨房漏水/屋顶天花板漏水/阳台漏水/地下室漏水防水补漏检测维修-正规防水补漏公司优选口碑榜测评推荐 - 即刻修防水
  • 简单理解:霍尔传感器 VS 编码器
  • 如何永久保存微信聊天记录:5分钟掌握数据留痕终极方案
  • 2026年当下,陕西企业如何精准联系优质夹胶玻璃品牌服务商? - 品牌鉴赏官2026
  • 2026盐城2026正规漏水检测维修公司精选口碑榜TOP5权威推荐-精准定位检测漏水点-专业防水补漏堵漏维修、卫生间/厨房/屋顶/天沟/地下室/阳台防水漏水检测维修 - 安佳防水
  • MC68F375总线异常处理:BERR、HALT与重试机制深度解析
  • Python开发项目部署:从本地到云端的完整流程
  • 性能测试脚本编写实战:从录制回放到精准压测的进阶指南
  • 2026 上海空调维修避坑指南 + 官方参考收费标准 - 星际AI
  • Git 从入门到实战——开发必备的版本控制技能
  • 2026年更新:两江新区全英文幼稚园新址揭晓,开启沉浸式双语教育新篇章 - 品牌鉴赏官2026
  • 深入解析MCU时钟与复位系统:PLL、看门狗与低功耗模式实战
  • Educational Codeforces Round 158 (Rated for Div. 2)D
  • 影刀RPA异常处理实战:Try-Catch正确用法
  • 麦克纳姆轮运动学模型:从原理到代码实现全向移动机器人底盘控制
  • Taurus性能测试平台:超越JMeter的自动化编排与CI/CD集成实践
  • 终极流媒体解析指南:猫抓cat-catch如何轻松突破MPD/DASH格式壁垒
  • 第四周总结
  • 2026年中河北地区民政救灾帐篷实力厂家深度解析与推荐 - 品牌鉴赏官2026
  • P值、置信度与统计决策:如何避免显著性检验的常见陷阱
  • 2026百色2026正规漏水检测维修公司精选口碑榜TOP5权威推荐-精准定位检测漏水点-专业防水补漏堵漏维修、卫生间/厨房/屋顶/天沟/地下室/阳台防水漏水检测维修 - 安佳防水
  • 一梦入盛唐,一醉下江南:淘宝直播×汾酒「汾享江南游园会」圆满收官
  • 青岛十家猫犬舍实测:3000㎡合规基地领跑,伴西西成养宠优选​ - 同城宠物优选基地
  • 深入解析MC68HC908AS32A的SCI模块:从异步通信原理到寄存器实战配置