SpringAOP专栏二《原理篇》
上一篇SpringAOP专栏一《使用教程篇》-CSDN博客介绍了SpringAop如何使用,这一篇文章就会介绍Spring AOP 的底层实现原理,并通过源代码解析来详细阐述其实现过程。
前言
Spring AOP 的实现原理是基于动态代理和字节码操作的。不了解动态代理和字节码操作的读者可以先看一下这篇文章java中的反射和代理模式-CSDN博客
实现原理
下面我会基于在使用SpringAOP进行逻辑增强时各个核心类的执行顺序进行底层原理的剖析
实现代理和切面逻辑的核心类执行顺序如下:
1.Bean 实例化阶段:
- BeanPostProcessor:在 Bean 实例化之后,进行初始化前的处理。其中,AbstractAutoProxyCreator 是一个重要的 BeanPostProcessor 实现类,用于自动创建代理对象。
2.切面织入阶段:
- AdvisedSupport:封装了切面逻辑和目标对象信息。
- ProxyFactoryBean:生成代理对象的工厂类。
- AopProxyFactory:AOP 代理对象的工厂接口。
- AopProxy:AOP 代理对象的核心接口,定义了获取代理对象的方法。
- CglibAopProxy:基于 CGLIB 的动态代理实现类。
- DynamicAdvisedInterceptor:CGLIB 动态代理的回调函数实现类,用于触发切面逻辑的执行。
3.方法拦截与执行阶段:
- ProxyMethodInvocation:代理方法调用的核心类,封装了目标对象方法和参数信息。
- ReflectiveMethodInvocation:反射调用目标方法的类,是 ProxyMethodInvocation 的具体实现类。
- MethodInvocationInterceptor:方法拦截器接口,定义了拦截器的方法执行逻辑。
- MethodInterceptor:CGLIB 库中的接口,被 MethodInvocationInterceptor 实现。
Bean 实例化阶段
在service bean的创建过程中(也就是getBean("service")
),AOP通过BeanPostProcess
后置处理器操作进行介入 分为2种情况:
- 用户自定义了
targetSource
,则bean的创建(实例化、填充、初始化)均由用户负责,Spring Ioc不会在管该代理目标对象traget,这种情况基本上不会发生,很多人用了几年Spring可能都不知道有它的存在- 正常情况下都是Spring Ioc完成代理对象target的实例化、填充、初始化。然后在初始化后置处理器中进行介入,对bean也就是service进行代理
下面是Bean示例化的流程图
切面织入阶段
Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。
在这个阶段会读取相关的SpringAOP的配置,如何将切面逻辑和目标对象信息封装到AdvisedSupport中,再通过ProxyFactoryBean(生成代理对象的工厂类)根据目标对象是否实现接口来调用JDK动态代理还是Cglib代理。
下面详细介绍一下两者代理方式的实现源码:
Spring AOP 动态代理实现:
- 默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。
- JDK Proxy(JDK 动态代理)
- CGLIB Proxy:默认情况下 Spring AOP 都会采用 CGLIB 来实现动态代理,因为效率高
- CGLIB 实现原理:通过继承代理对象来实现动态代理的(子类拥有父类的所有功能)
- CGLIB 缺点:不能代理最终类(也就是被 final 修饰的类)
JDK动态代理
JDK 动态代理是 Java 自带的动态代理实现方式。使用JDK动态代理时,需要目标对象实现至少一个接口。JDK 动态代理会在运行时生成一个实现了目标对象接口的代理类,该代理类会在目标对象方法执行前后插入切面代码。
下面是JdkDynamicAopProxy类的部分源码,感兴趣的读者可以自己去看看,我这里就不一一截图给大家看了。
如果读者看源码如果有困难,可以看一下这个我简化了的JdkDynamicAopProxy类。这个类我保留了主要逻辑,把一些提高代码健壮性的部分去掉了。
JdkDynamicAopProxy 类的主要作用是将切面逻辑织入到目标对象的方法调用中。当使用基于接口的代理方式时,Spring AOP 使用 JDK 动态代理来创建代理对象。JdkDynamicAopProxy 类会根据配置的切面信息,动态地生成一个代理对象,并将切面逻辑织入到该代理对象的方法调用中。
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {private final AdvisedSupport advised;public JdkDynamicAopProxy(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object getProxy() {return Proxy.newProxyInstance(getClass().getClassLoader(),advised.getTargetSource().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInterceptor methodInterceptor = advised.getMethodInterceptor();MethodInvocation methodInvocation = new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method,args,methodInterceptor,advised.getTargetSource().getTargetClass());return methodInvocation.proceed();}
}
在该代码中,JdkDynamicAopProxy 类实现了 AopProxy 和 InvocationHandler 接口。
AopProxy 接口是 Spring AOP 提供的代理接口,它定义了获取代理对象的方法。JdkDynamicAopProxy 类通过实现该接口,提供了基于 JDK 动态代理的代理对象获取功能。
InvocationHandler 接口是 JDK 提供的反射 API 中的一部分,它定义了一个 invoke 方法,用于在代理对象上调用被代理方法。JdkDynamicAopProxy 类实现了 InvocationHandler 接口,通过重写 invoke 方法,实现对被代理方法的增强逻辑。
下面是对简化了的JdkDynamicAopProxy类的详细解读
AdvisedSupport
类型的属性advised
:表示该代理对象所依赖的AdvisedSupport
对象,该对象包含了切面配置信息。构造函数
JdkDynamicAopProxy(AdvisedSupport advised)
:接收一个AdvisedSupport
对象作为参数,并将其赋值给advised
属性。
getProxy()
方法:实现了AopProxy
接口中的方法,用于获取代理对象。它通过调用Proxy.newProxyInstance()
方法创建代理对象。
getClass().getClassLoader()
获取当前类的类加载器。advised.getTargetSource().getInterfaces()
获取目标对象实现的接口数组。this
表示使用当前对象作为代理对象的InvocationHandler
。
invoke(Object proxy, Method method, Object[] args)
方法:实现了InvocationHandler
接口中的方法,拦截目标对象方法的调用并进行增强逻辑。
- 首先,从
advised
对象中获取MethodInterceptor
实例,即切面逻辑。- 然后,创建一个
ReflectiveMethodInvocation
实例,传入目标对象、目标方法、方法参数、切面逻辑和目标对象的类信息。- 最后,调用
methodInvocation.proceed()
方法执行切面逻辑,并返回方法的执行结果。
CGLIB 代理
CGLIB 代理是一个基于字节码操作的代理方式,它可以为没有实现接口的类创建代理对象。CGLIB 代理会在运行时生成一个目标对象的子类,并覆盖其中的方法,以实现AOP的功能。
下面是CglibAopProxy类的部分源代码,感兴趣的读者可以自己去看看,我这里就不一一截图给大家看了。
如果读者看源码如果有困难,可以看一下这个我简化了的CglibAopProxy类。这个类我保留了主要逻辑,对代码进行了简化。
public class CglibAopProxy implements AopProxy {private final AdvisedSupport advised;public CglibAopProxy(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(advised.getTargetSource().getTargetClass());enhancer.setCallback(new DynamicAdvisedInterceptor(advised));return enhancer.create();}private static class DynamicAdvisedInterceptor implements MethodInterceptor {private final AdvisedSupport advised;public DynamicAdvisedInterceptor(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {MethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(),method,args,proxy,advised.getMethodInterceptor(),advised.getTargetSource().getTargetClass());return methodInvocation.proceed();}}
}
CglibAopProxy 类主要有以下作用
生成代理对象:CglibAopProxy 根据目标对象和切面逻辑生成一个代理对象。与 JDK 动态代理不同,CGLIB 代理不需要目标对象实现接口。
增强方法逻辑:通过继承目标对象,CglibAopProxy 重写目标对象的方法,并在方法中添加切面逻辑。这样,在调用代理对象的方法时,会先执行切面逻辑,然后再调用目标对象的原始方法。
拦截方法调用:CglibAopProxy 使用 MethodInterceptor 接口来定义切面逻辑,并将其应用于生成的代理对象。在代理对象的方法调用过程中,切面逻辑会被触发,从而实现横切关注点的功能,如事务管理、日志记录等。
高性能的代理:相较于 JDK 动态代理,CGLIB 代理使用了字节码生成技术,生成的代理对象是目标对象的子类。这种方式避免了通过反射调用目标对象的方法,提供了更高的执行性能。
下面是CglibAopProxy类的详细解读
CglibAopProxy 类实现了 AopProxy 接口,该接口定义了获取代理对象的方法 getProxy()。
CglibAopProxy 类的构造方法需要一个 AdvisedSupport 对象作为参数,AdvisedSupport 是 Spring AOP 框架中的核心类,用于保存切面逻辑和目标对象信息。
getProxy() 方法中,首先实例化了 Enhancer 对象,Enhancer 是 CGLIB 库中的主要类,用于生成代理对象。
setSuperclass() 方法将目标对象的类设置为要生成的代理类的父类,这样代理类就可以继承目标类的所有非私有方法。
setCallback() 方法用于设置代理类的回调函数,即在代理类的方法调用时,会触发回调函数中的逻辑。
DynamicAdvisedInterceptor 类实现了 MethodInterceptor 接口,这个接口是 CGLIB 库中的接口,用于定义代理类的回调函数。
intercept() 方法是 DynamicAdvisedInterceptor 类的核心方法。当代理类的方法被调用时,intercept() 方法会被触发。在该方法中,首先将目标对象的方法和参数封装成 MethodInvocation 对象,然后调用其 proceed() 方法,从而触发切面逻辑的执行。
CglibMethodInvocation 类是 MethodInvocation 接口的实现类,用于封装目标对象的方法和参数信息。
方法拦截与执行阶段
该阶段的实际执行流程为:
当代理对象的方法被调用时,会创建一个 ProxyMethodInvocation 对象,并将目标对象、目标方法、方法参数等信息传递给它。
ProxyMethodInvocation 继承了 ReflectiveMethodInvocation 类,因此它可以通过反射调用目标方法。
在拦截器链的创建过程中,AdvisorChainFactory 会根据切点和通知创建 Advisor 链,即将所有与目标方法匹配的切面的方法拦截器添加到拦截器链中。
当 ProxyMethodInvocation 执行目标方法时,它会依次遍历拦截器链中的每个方法拦截器。
每个方法拦截器在方法调用前后执行自己的逻辑,可以实现前置通知、后置通知、异常处理和返回通知等功能。
方法拦截器的执行顺序与它们在拦截器链中的顺序一致。在方法调用之前,拦截器依次执行前置通知;在方法调用之后,拦截器依次执行后置通知;如果方法发生异常,拦截器执行异常处理逻辑;最后,拦截器执行返回通知。
下面我来分析一下SpringAOP的拦截器
SpringAOP的拦截器
我们先来了解一下拦截器的执行属性:如下图所示
接下来看一下AOP拦截器执行原理,拦截器是如何保证不同通知注解下的方法的执行顺序的呢?
在 Spring AOP 中,拦截器链的执行顺序是由 AdvisedSupport 类中的方法获取的。AdvisedSupport 包含了代理对象需要的所有信息,包括目标对象、代理接口、拦截器等。其中,拦截器链的创建和执行是在 ExposeInvocationInterceptor 这个拦截器中完成的。
在创建拦截器链时,AdvisorChainFactory 会根据切面的顺序将各个切面的拦截器顺序组合成一个拦截器链。
这样就可以保证 before 在 after 之前执行,因为在创建拦截器链的过程中,会按照切面的顺序将各个切面的拦截器依次添加到链中,最终形成一个有序的拦截器链。
另外,对于同一个方法的多个切面,Spring AOP 会根据切面的顺序将它们的拦截器依次添加到拦截器链中,从而保证了它们的执行顺序。这样就可以保证 find 方法的执行顺序符合切面的定义顺序。
因此,通过 Spring AOP 框架内部对拦截器链的创建和执行机制的设计,可以保证拦截器的执行顺序满足业务需求,确保了 before 在 after 之前执行,并且保证了多个拦截器的执行顺序。
相关文章:

SpringAOP专栏二《原理篇》
上一篇SpringAOP专栏一《使用教程篇》-CSDN博客介绍了SpringAop如何使用,这一篇文章就会介绍Spring AOP 的底层实现原理,并通过源代码解析来详细阐述其实现过程。 前言 Spring AOP 的实现原理是基于动态代理和字节码操作的。不了解动态代理和字节码操作…...

冒泡排序(函数)
冒泡排序,将一个列表中的两个元素进行比较,并将最小的元素交换到顶部。两个元素中较小的会冒到顶部,而较大的会沉到底部,该过程将被重复执行,直到所有元素都被排序。 输入格式: 输入在第1行中给出N(1<N…...

Vue3中的defineModel
目录 一、vue3的defineModel介绍 二、defineModel使用 (1)在vite.config.js中开启 (2)子组件 (3)父组件 一、vue3的defineModel介绍 为什么要使用到defineModel呢?这里有这样一种场景&…...

动态内存管理(C语言)
前言 在学习数据结构时,掌握指针、结构体和动态内存管理是非常关键的,它们就像是搭建程序框架和操作内存的工具箱,需要熟练掌握才能更加游刃有余地进行编程。 指针和结构体我们已经在之前详细的讲过了,今天我将带大家学习动态内存…...

区块链实验室(32) - 下载arm64的Prysm
Prysm是Ethereum的共识层。 1. 下载prysm.sh curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh && chmod x prysm.sh2. 下载x86版prysm共识客户端 ./prysm.sh beacon-chain --download-only3.下载arm64版prysm共识客…...
flutter学习-day3-dart基础
📚 目录 变量声明操作符数据类型控制流错误处理和捕获函数mixin异步 FutureStream 本文学习和引用自《Flutter实战第二版》:作者:杜文 1. 变量声明 var 类似于 JavaScript 中的var,它可以接收任何类型的变量,但最大…...
gitblit自建git仓库
在Ubuntu服务器 安装 java sudo apt-get update sudo apt-get install openjdk-8-jdk # 或者其它你喜欢的版本//sudo snap install gitblit 验证: java -version下载 gitblit https://github.com/gitblit-org/gitblit/releases解压/usr/local tar -zxvf gitb…...

二百一十一、Flume——Flume实时采集Linux中的Hive日志写入到HDFS中(亲测、附截图)
一、目的 为了实现用Flume实时采集Hive的操作日志到HDFS中,于是进行了一场实验 二、前期准备 (一)安装好Hadoop、Hive、Flume等工具 (二)查看Hive的日志在Linux系统中的文件路径 [roothurys23 conf]# find / -name…...

python 实现 AIGC 大模型中的概率论:生日问题的基本推导
在上一节中,我们对生日问题进行了严谨的阐述:假设屋子里面每个人的生日相互独立,而且等可能的出现在一年 365 天中的任何一天,试问我们需要多少人才能让某两个人的生日在同一天的概率超过 50%。 处理抽象逻辑问题的一个入手点就是…...
YOLOv8算法改进【NO.87】引入上下文引导网络(CGNet)的Light-weight Context Guided改进C2_f
前 言 YOLO算法改进系列出到这,很多朋友问改进如何选择是最佳的,下面我就根据个人多年的写作发文章以及指导发文章的经验来看,按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通: 第一,创新主干特征提取网络,将整个Backbone改…...

GPT-4V 在机器人领域的应用
在科技的浩渺宇宙中,OpenAI如一颗璀璨的星辰,于2023年9月25日,以一种全新的方式,向世界揭示了其最新的人工智能力作——GPT-4V模型。这次升级,为其旗下的聊天机器人ChatGPT装配了语音和图像的新功能,使得用…...

Java基础语法之访问修饰限定符
private 表示私有的,只能在同一个包中的同一个类使用 像这样就是在同一个包中的不同类用了private修饰的变量,这是非法的,那到底该如何给a赋值呢?可以在定义时就赋值,但这样的代码就没有可操作性,所以我们…...
算法通关村第十八关 | 青铜 | 回溯
1.回溯 回溯可以视为递归的拓展,有着明确的解题模板。 很大的不同之处是有一个撤销处理结果的操作,但是大框架就是遍历 N 叉树。 回溯主要解决暴力枚举都解决不了的问题。 回溯模板: void backtracking(参数) {if (终止条件) {存放结果;…...

蓝牙在物联网中的应用,相比WIFI和NFC的优势?
蓝牙在物联网中有着广泛的应用,主要包括以下几个方面: 1、智能家居:蓝牙Mesh技术可以用于智能家居设备之间的连接和通信,实现设备的远程控制和管理。例如,通过蓝牙技术可以将智能音箱、智能电视、智能家电等设备连接起…...

Altair推出 Altair RapidMiner 2023 平台,提供生成式 AI 功能
Altair推出 Altair RapidMiner 2023 平台,提供生成式 AI 功能 更新包括自动聚类、扩展 SAS、Python 和 R 编程功能等 近日,Altair(纳斯达克股票代码:ALTR)近日宣布其数据分析和 AI 平台 Altair RapidMiner 取得了一系…...
包管理工具npm与yarn
1.npm 1.1 安装 安装node后自带了npm 2.2 初始化package.json npm init 1.3 安装包 单个包:npm install less或npm i less 所有包:npm installnpm i 1.4 删除包 npm remove less,npm r less或npm uninstall less 1.5 配置别名 pack…...

深度学习 Day11——T11优化器对比实验
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 | 接辅导、项目定制 文章目录 前言一、我的环境二、代码实现与执行结果1.引入库2.设置GPU(如果使用的是CPU可以忽略这步)3.导入数据4.查…...

(十六)Flask之蓝图
蓝图 Flask蓝图(Blueprint)是Flask框架中用于组织和管理路由、视图函数以及静态文件的一种机制。它提供了一种将应用程序拆分为更小、可重用组件的方式,使得项目结构更清晰,代码更易于维护。 使用Flask蓝图,可以将相…...
面试问题--文件IO
文件 I/O 操作在 C 语言中的使用 在 C 语言中,文件 I/O(Input/Output)操作是处理文件的重要部分。本文将介绍一些常见的文件 I/O 操作及其使用示例。 打开和关闭文件 1.打开文件: fopen() 函数用于打开一个文件。 FILE *fpt…...
SpringBoot中实现跨域的几种常用方式
在SpringBoot中实现跨域请求可以通过以下几种方式: 1. 使用CrossOrigin注解,可以直接在Controller层的方法上使用,用来指定允许跨域请求的来源、方法和头信息。例如: CrossOrigin(origins "http://localhost:8080") …...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...

Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...