Spring Boot AOP(二) 代理机制解析

Spring Boot AOP(二) 代理机制解析
博主社群介绍: ① 群内初中生、高中生、本科生、研究生、博士生遍布,可互相学习,交流困惑。 ② 热榜top10的常客也在群里,也有数不清的万粉大佬,可以交流写作技巧,上榜经验,涨粉秘籍。 ③ 群内也有职场精英,大厂大佬,跨国企业主管,可交流技术、面试、找工作的经验。 进群免费赠送写作秘籍一份,助你由写作小白晋升为创作大佬,进群赠送ZEEKLOG评论防封脚本,送真活跃粉丝,助你提升文章热度。 群公告里还有全网大赛约稿汇总/博客提效工具集/ZEEKLOG自动化运营脚本 有兴趣的加文末联系方式,备注自己的ZEEKLOG昵称,拉你进群,互相学习共同进步。 

文章目录


Spring Boot AOP(二) 代理机制解析

1. 代理机制概述

Spring AOP 的核心在于 代理对象,它负责在方法调用前后织入切面逻辑。Spring AOP 默认只对 Spring 管理的 Bean 生效,并且使用 运行时动态代理(JDK 动态代理或 CGLIB 代理)。

代理类型特点使用场景限制
JDK 动态代理基于接口生成代理对象Bean 实现接口只能代理接口方法
CGLIB 代理基于子类生成代理对象Bean 无接口或 proxyTargetClass=truefinal 类或 final 方法无法代理
ByteBuddy(Spring 5 可选)生成字节码动态代理高级场景复杂配置,可替代 CGLIB
Spring Boot 默认自动选择 JDK 或 CGLIB,除非手动配置 proxyTargetClass=true 强制使用 CGLIB。

2. JDK 动态代理源码解析

JDK 动态代理基于 java.lang.reflect.ProxyInvocationHandler 实现。

核心类和方法

Object proxy =Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(),newInvocationHandler(){@OverridepublicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{System.out.println("方法调用前");Object result = method.invoke(target, args);System.out.println("方法调用后");return result;}});

流程示意

客户端调用代理对象方法InvocationHandler.invoke切面前置逻辑调用目标对象方法切面返回/异常逻辑返回调用方

特点

  • 代理对象和目标对象实现相同接口
  • 方法调用通过 InvocationHandler 转发
  • 执行链由多个 Advice 组合而成

3. CGLIB 代理源码解析

CGLIB(Code Generation Library)通过 生成目标类的子类,在方法调用中织入切面逻辑。

核心类

  • Enhancer:创建代理类
  • MethodInterceptor:拦截方法调用
  • CallbackFilter:控制哪些方法需要拦截
Enhancer enhancer =newEnhancer(); enhancer.setSuperclass(TargetClass.class); enhancer.setCallback(newMethodInterceptor(){@OverridepublicObjectintercept(Object obj,Method method,Object[] args,MethodProxy proxy)throwsThrowable{System.out.println("方法调用前");Object result = proxy.invokeSuper(obj, args);System.out.println("方法调用后");return result;}});TargetClass proxy =(TargetClass) enhancer.create();

调用流程

flowchart TD A[客户端调用代理对象方法] --> B[MethodInterceptor.intercept] B --> C{是否有前置通知?} C -->|是| D[@Before 前置逻辑] C -->|否| E[调用目标对象方法] D --> E E --> F{方法是否抛异常?} F -->|否| G[@AfterReturning 返回通知] F -->|是| H[@AfterThrowing 异常通知] G --> I[@After 后置通知] H --> I I --> J[返回调用方] style A fill:#BBDEFB style B fill:#FFF9C4 style E fill:#FFCDD2 style G fill:#C8E6C9 style H fill:#FFE0B2 style J fill:#E1BEE7 

特点

  • 生成目标类子类,支持无接口类
  • 不能代理 final 类或 final 方法
  • 方法调用速度略快于 JDK 动态代理

4. Spring AOP 代理选择机制

Spring 自动选择代理类型:

条件结果
Bean 实现接口且 proxyTargetClass=false使用 JDK 动态代理
Bean 无接口或 proxyTargetClass=true使用 CGLIB 代理
@EnableAspectJAutoProxy(proxyTargetClass =true)// 强制使用 CGLIB

Mermaid 流程:代理选择逻辑

是是否否创建 Bean 代理Bean 实现接口?proxyTargetClass=true?使用 CGLIB 代理使用 JDK 代理


5. Spring 代理生成核心源码解析

5.1 入口类

AnnotationAwareAspectJAutoProxyCreator(AOP 自动代理器)负责:

  1. 扫描 Bean,判断是否匹配切面
  2. 生成 Advisor(切入点 + 通知)
  3. 根据条件选择代理类型
  4. 使用 ProxyFactoryEnhancer 创建代理对象

是否BeanPostProcessor.postProcessAfterInitialization是否匹配切面?创建 ProxyFactory选择代理类型 JDK/CGLIB生成代理对象替换原 Bean 注入容器直接返回 Bean

5.2 ProxyFactory 核心方法

protectedObjectcreateProxy(BeanFactory beanFactory,Object target,String beanName){ProxyFactory proxyFactory =newProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAdvisors(this.findAdvisors(beanFactory, beanName));return proxyFactory.getProxy(getProxyClassLoader());}

6. 方法调用链源码解析

Spring AOP 方法调用链(以 JDK/CGLIB 统一):

  1. 客户端调用代理对象方法
  2. ReflectiveMethodInvocation 封装调用信息
  3. 执行 Advisor 链:
    • MethodBeforeAdvice → AroundAdvice → AfterReturning/AfterThrowing
  4. 最终调用目标方法
  5. 返回结果或异常传递给代理

客户端调用代理方法ReflectiveMethodInvocation.proceedAdvisor 链: MethodBeforeAdviceAdvisor 链: AroundAdvice调用目标方法Advisor 链: AfterReturning/AfterThrowing返回客户端


7. 实战示例:多切面组合

@Aspect@Component@Order(1)publicclassLoggingAspect{@Before("execution(* com.example.service..*.*(..))")publicvoidlogBefore(JoinPoint jp){System.out.println("日志切面前置通知: "+ jp.getSignature());}}@Aspect@Component@Order(2)publicclassPerformanceAspect{@Around("execution(* com.example.service..*.*(..))")publicObjectmeasureTime(ProceedingJoinPoint pjp)throwsThrowable{long start =System.currentTimeMillis();Object result = pjp.proceed();System.out.println("性能切面耗时: "+(System.currentTimeMillis()- start)+"ms");return result;}}

多切面调用顺序示意

flowchart TD A[方法调用] --> B[LoggingAspect @Before] B --> C[PerformanceAspect @Around 前] C --> D[目标方法执行] D --> E[PerformanceAspect @Around 后] E --> F[返回调用方] 

8. 总结

  • Spring AOP 核心是 代理对象
  • JDK 动态代理针对接口,CGLIB 针对类
  • Spring 自动选择代理类型,可配置 proxyTargetClass
  • AnnotationAwareAspectJAutoProxyCreator + ProxyFactory 是生成代理的核心
  • 方法调用链由 Advisor 链统一管理,实现通知执行顺序
  • Mermaid 流程图直观展示代理生成和方法调用链


结束语

👨‍💻 关于我

持续学习 | 追求真我

如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的。想看更多 那就点个关注吧 我会尽力带来有趣的内容 😎。

感谢订阅专栏 三连文章

image-20251011155556997

掘金点击访问QiunerZEEKLOG点击访问QiunerGitHub点击访问QiunerGitee点击访问Qiuner

专栏简介
📊 一图读懂系列图文并茂,轻松理解复杂概念
📝 一文读懂系列深入浅出,全面解析技术要点
🌟持续更新保持学习,不断进步
🎯 人生经验经验分享,共同成长
你好,我是Qiuner. 为帮助别人少走弯路而写博客

如果本篇文章帮到了你 不妨点个吧~ 我会很高兴的 😄 (^ ~ ^) 。想看更多 那就点个关注吧 我会尽力带来有趣的内容 😎。

代码都在Github或Gitee上,如有需要可以去上面自行下载。记得给我点星星哦😍

如果你遇到了问题,自己没法解决,可以去我掘金评论区问。ZEEKLOG评论区和私信消息看不完 掘金消息少一点.
上一篇推荐链接
Java程序员快又扎实的学习路线点击该处自动跳转查看哦
一文读懂 AI点击该处自动跳转查看哦
一文读懂 服务器点击该处自动跳转查看哦
2024年创作回顾点击该处自动跳转查看哦
一文读懂 ESLint配置点击该处自动跳转查看哦
老鸟如何追求快捷操作电脑点击该处自动跳转查看哦
未来会写什么文章?预告链接
一文读懂 XX?点击该处自动跳转查看哦
2025年终总结点击该处自动跳转查看哦
一图读懂 XX?点击该处自动跳转查看哦

关于"掰开揉碎讲编程"系列
编程的世界常常让初学者望而生畏——晦涩的术语、抽象的概念、复杂的原理,像是一座座难以逾越的高山。但学习编程,本不该如此艰难。

"掰开揉碎讲编程"系列的初衷,就是把那些看似高深的技术知识,像掰开面包一样拆解开来,像揉碎面团一样细细讲透。这里不玩虚的,不堆砌术语,只用最朴实的语言、最贴近生活的比喻,再搭配手绘般的图解示意。抽象的概念画出来,复杂的流程拆开看,让编程知识变得像看图说话一样简单。

与其他基础教程不同的是,我不会上来就告诉你"怎么装、怎么用"。每一个工具、每一项技术,我都会带你了解它的前世今生——它诞生的背景、要解决的痛点、在整个开发流程中的位置。只有理解了"为什么需要它",才能真正掌握"如何用好它"。

内容上,这个系列会有两种文章:

一种是长篇深度文,慢工出细活,把一个技术从头到尾讲清楚——它怎么来的、为什么重要、怎么用、怎么用好。适合系统学习,打牢基础。

另一种是短篇问题文,专治各种疑难杂症——IDEA汉化后乱码了、Git冲突不知道怎么解、环境变量配置出了岔子等等。遇到问题时翻一翻,快速解决,继续开发。

这里没有"懂的都懂"式的敷衍,没有"显而易见"的跳跃,每一个概念都会从零开始构建,每一处难点都会反复推敲。就像老师傅手把手教徒弟,我想做的,是让每一个想学编程的人,都能真正理解技术背后的本质。

无论你是刚接触编程的萌新,还是想要夯实基础的开发者,这个系列都希望成为你的良师益友。让我们一起,把编程这件事,掰开了、揉碎了,彻彻底底搞明白。

Read more

华为OD技术面八股文真题_C++_3

华为OD技术面八股文真题_C++_3

文章目录 * 变量的声明和定义的区别 * 内存泄露是什么意思?怎么避免内存泄露 * 怎么排查内存泄漏,遇到内存泄漏情况,一般怎么解决 * 说一下define和const的区别 * define和typedef的区别 * 宏函数和内联函数的区别 * 类和结构体的区别 * 结构体(struct)和联合体(union)差别 * 静态库和动态库区别 * 介绍一下C++的编译过程 变量的声明和定义的区别 * 变量的声明是告诉编译器变量的名称和类型,不分配存储空间; * 变量的定义会为变量分配存储空间并建立实体。 * 一个变量可以在多个地方声明,但只能在一个地方定义。 使用 extern 修饰的变量通常是声明,表示该变量在其它文件中定义,但 如果 extern 变量带初始化,则该语句仍然属于定义。 内存泄露是什么意思?怎么避免内存泄露 内存泄漏是指程序在动态申请内存后,后续失去对该内存的控制,导致这块内存无法被释放,从而造成内存资源浪费的现象。内存被申请了,却释放不了。 内存泄漏的危害如下: 1. 程序内存占用不断增大,导致系统可用内存减少,性能下

By Ne0inhk
C++ 二叉搜索树全解析!增删查改 + key/value 场景 + 完整代码,一篇通关

C++ 二叉搜索树全解析!增删查改 + key/value 场景 + 完整代码,一篇通关

✨ 孤廖:个人主页 🎯 个人专栏:《C++:从代码到机器》 🎯 个人专栏:《Linux系统探幽:从入门到内核》 🎯 个人专栏:《算法磨剑:用C++思考的艺术》 折而不挠,中不为下 文章目录 * 正文: * 1. ⼆叉搜索树的概念 * 2. ⼆叉搜索树的性能分析 * 3. ⼆叉搜索树的插⼊ * 4. ⼆叉搜索树的查找 * 5. ⼆叉搜索树的删除 * 6. ⼆叉搜索树key和key/value使⽤场景 * 6.1 key搜索场景: * 6.2 key/val搜索场景 * 7. ⼆叉搜索树的实现代码 * 7.1 key模型代码实现 * 7.2 key/val代码实现 * 结语 正文: 1. ⼆叉搜索树的概念

By Ne0inhk
C++中的父继子承:继承方式实现栈及同名隐藏和函数重载的本质区别, 派生类的4个默认成员函数

C++中的父继子承:继承方式实现栈及同名隐藏和函数重载的本质区别, 派生类的4个默认成员函数

🎬 胖咕噜的稞达鸭:个人主页 🔥 个人专栏: 《数据结构》《C++初阶高阶》《算法入门》 ⛺️技术的杠杆,撬动整个世界! 学习完本文,你将知道:(各位大佬预知答案几何请移步文章结尾!) 1. 当子类继承了父类,父类的私有成员在子类中是不可见的,所以父类的私有成员在子类中有没有被继承下来? 2. 子类对象一定比父类大? 3. 函数重载和函数隐藏的区别是什么?同名了有什么影响? 4. 派生类构造函数初始化列表的位置必须显式调用基类的构造函数,已完成基类部分成员的初始化? 5. 派生类构造函数先初始化子类成员,再初始化基类成员?派生类对象构造函数先调用子类构造函数,在调用基类构造函数? 接着来步入今天的正文: 面向对象三大特性:封装,继承,多态 我们之前学过了封装,类的定义是一个封装,迭代器实现也是一个封装,屏蔽了底层的实现细节。模板的使用也是一个封装。接下来讲解面向对象第二大特性:继承。 继承的定义: 假设大学学生和大学的老师,作为一个人的共性,都有姓名,住址和电话号码,但是不同的是,老师授课有职称,学生有学号,这是老师和学生不同的地方。

By Ne0inhk
【 C/C++ 算法】入门动态规划-----路径问题(以练代学式)

【 C/C++ 算法】入门动态规划-----路径问题(以练代学式)

>每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论 : 本章是动态规划的第二篇,本章将开始二维的动态规划,在二维中的动态规划本质和一维的分析来说差不太多,只不过状态表示从一维变成了二维,而在二维上所能管理的状态就从一维的两个变成了二维的三个,也就是x轴,y轴,数组中的值。若没看了解过动规算法,我强烈建议先看第一篇blog,因为当你看完第一篇你就对动规基本认识了,其中也就能认识到它的五步骤分析法,这里也就不扩充说明而是直接使用了 ———————— 早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。 路径问题🛣️ 本章主要还是在二维数组中的进行的动态规划: 同样还是五步走:状态表示、状态方程、初始化、移动方向、返回结果 1. 其中在二维中状态表示就会和一位略有不同,不同本质一样: 从以 i 结尾.,… ==》从左上角到达 i j 位置,… 1. 当然在最后一题中发现上面这种常规方法实现不通,因为状态方程会受后面状态影响 2.

By Ne0inhk