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 类实现了 AopProxyInvocationHandler 接口。

AopProxy 接口是 Spring AOP 提供的代理接口,它定义了获取代理对象的方法。JdkDynamicAopProxy 类通过实现该接口,提供了基于 JDK 动态代理的代理对象获取功能。

InvocationHandler 接口是 JDK 提供的反射 API 中的一部分,它定义了一个 invoke 方法,用于在代理对象上调用被代理方法。JdkDynamicAopProxy 类实现了 InvocationHandler 接口,通过重写 invoke 方法,实现对被代理方法的增强逻辑。

下面是对简化了的JdkDynamicAopProxy类的详细解读

  1. AdvisedSupport 类型的属性 advised:表示该代理对象所依赖的 AdvisedSupport 对象,该对象包含了切面配置信息。

  2. 构造函数 JdkDynamicAopProxy(AdvisedSupport advised):接收一个 AdvisedSupport 对象作为参数,并将其赋值给 advised 属性。

  3. getProxy() 方法:实现了 AopProxy 接口中的方法,用于获取代理对象。它通过调用 Proxy.newProxyInstance() 方法创建代理对象。

    • getClass().getClassLoader() 获取当前类的类加载器。
    • advised.getTargetSource().getInterfaces() 获取目标对象实现的接口数组。
    • this 表示使用当前对象作为代理对象的 InvocationHandler
  4. 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类的详细解读

  1. CglibAopProxy 类实现了 AopProxy 接口,该接口定义了获取代理对象的方法 getProxy()。

  2. CglibAopProxy 类的构造方法需要一个 AdvisedSupport 对象作为参数,AdvisedSupport 是 Spring AOP 框架中的核心类,用于保存切面逻辑和目标对象信息。

  3. getProxy() 方法中,首先实例化了 Enhancer 对象,Enhancer 是 CGLIB 库中的主要类,用于生成代理对象。

  4. setSuperclass() 方法将目标对象的类设置为要生成的代理类的父类,这样代理类就可以继承目标类的所有非私有方法。

  5. setCallback() 方法用于设置代理类的回调函数,即在代理类的方法调用时,会触发回调函数中的逻辑。

  6. DynamicAdvisedInterceptor 类实现了 MethodInterceptor 接口,这个接口是 CGLIB 库中的接口,用于定义代理类的回调函数。

  7. intercept() 方法是 DynamicAdvisedInterceptor 类的核心方法。当代理类的方法被调用时,intercept() 方法会被触发。在该方法中,首先将目标对象的方法和参数封装成 MethodInvocation 对象,然后调用其 proceed() 方法,从而触发切面逻辑的执行。

  8. CglibMethodInvocation 类是 MethodInvocation 接口的实现类,用于封装目标对象的方法和参数信息。

方法拦截与执行阶段

该阶段的实际执行流程为:

  1. 当代理对象的方法被调用时,会创建一个 ProxyMethodInvocation 对象,并将目标对象、目标方法、方法参数等信息传递给它。

  2. ProxyMethodInvocation 继承了 ReflectiveMethodInvocation 类,因此它可以通过反射调用目标方法。

  3. 在拦截器链的创建过程中,AdvisorChainFactory 会根据切点和通知创建 Advisor 链,即将所有与目标方法匹配的切面的方法拦截器添加到拦截器链中。

  4. 当 ProxyMethodInvocation 执行目标方法时,它会依次遍历拦截器链中的每个方法拦截器。

  5. 每个方法拦截器在方法调用前后执行自己的逻辑,可以实现前置通知、后置通知、异常处理和返回通知等功能。

  6. 方法拦截器的执行顺序与它们在拦截器链中的顺序一致。在方法调用之前,拦截器依次执行前置通知;在方法调用之后,拦截器依次执行后置通知;如果方法发生异常,拦截器执行异常处理逻辑;最后,拦截器执行返回通知。

下面我来分析一下SpringAOP的拦截器

SpringAOP的拦截器

我们先来了解一下拦截器的执行属性:如下图所示

接下来看一下AOP拦截器执行原理,拦截器是如何保证不同通知注解下的方法的执行顺序的呢?

在 Spring AOP 中,拦截器链的执行顺序是由 AdvisedSupport 类中的方法获取的。AdvisedSupport 包含了代理对象需要的所有信息,包括目标对象、代理接口、拦截器等。其中,拦截器链的创建和执行是在 ExposeInvocationInterceptor 这个拦截器中完成的。

在创建拦截器链时,AdvisorChainFactory 会根据切面的顺序将各个切面的拦截器顺序组合成一个拦截器链。这样就可以保证 before 在 after 之前执行,因为在创建拦截器链的过程中,会按照切面的顺序将各个切面的拦截器依次添加到链中,最终形成一个有序的拦截器链。

另外,对于同一个方法的多个切面,Spring AOP 会根据切面的顺序将它们的拦截器依次添加到拦截器链中,从而保证了它们的执行顺序。这样就可以保证 find 方法的执行顺序符合切面的定义顺序。

因此,通过 Spring AOP 框架内部对拦截器链的创建和执行机制的设计,可以保证拦截器的执行顺序满足业务需求,确保了 before 在 after 之前执行,并且保证了多个拦截器的执行顺序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/207697.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】函数递归详解(一)

目录 1.什么是递归: 1.1递归的思想: 1.2递归的限制条件: 2.递归举例: 2.1举例1:求n的阶乘: 2.1.1 分析和代码实现: 2.1.2图示递归过程: 2.2举例2:顺序打印一个整数的…

机器学习---集成学习的初步理解

1. 集成学习 集成学习(ensemble learning)是现在非常火爆的机器学习方法。它本身不是一个单独的机器学 习算法,而是通过构建并结合多个机器学习器来完成学习任务。也就是我们常说的“博采众长”。集 成学习可以用于分类问题集成,回归问题集成&#xff…

多线程并发Ping脚本

1. 前言 最近需要ping地址,还是挺多的,就使用python搞一个ping脚本,记录一下,以免丢失了。 2. 脚本介绍 首先检查是否存在True.txt或False.txt文件,并在用户确认后进行删除,然后从IP.txt的文件中读取IP地…

CSS——sticky定位

1. 大白话解释sticky定位 粘性定位通俗来说,它就是相对定位relative和固定定位fixed的结合体,它的触发过程分为三个阶段 在最近可滚动容器没有触发滑动之前,sticky盒子的表现为相对定位relative【第一阶段】, 但当最近可滚动容…

【MATLAB】tvfEMD信号分解+FFT+HHT组合算法

有意向获取代码,请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 TVFEMDFFTHHT组合算法是一种结合了总体变分模态分解(TVFEMD)、傅里叶变换(FFT)和希尔伯特-黄变换(HHT)的信号分解方…

电子学会C/C++编程等级考试2021年06月(五级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:数字变换 给定一个包含5个数字(0-9)的字符串,例如 “02943”,请将“12345”变换到它。 你可以采取3种操作进行变换 1. 交换相邻的两个数字 2. 将一个数字加1。如果加1后大于9,则变为0 3. 将一个数字加倍。如果加倍后大于…

Python configparser 模块:优雅处理配置文件的得力工具

更多资料获取 📚 个人网站:ipengtao.com 配置文件在软件开发中扮演着重要的角色,而Python中的 configparser 模块提供了一种优雅而灵活的方式来处理各种配置需求。本文将深入介绍 configparser 模块的各个方面,通过丰富的示例代码…

嵌入式杂记 - MDK的Code, RO-data , RW-data, ZI-data意思

嵌入式杂记 - Keil的Code, RO-data , RW-data, ZI-data意思 MDK中的数据分类MCU中的内部存储分布MDK中数据类型存储Code代码段例子 RO-data 只读数据段例子 RW-data 可读写数据段例子 ZI-data 清零数据段例子 在嵌入式开发中,我们经常都会使用一些IDE,例…

Hadoop学习笔记(HDP)-Part.17 安装Spark2

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

Web前端 ---- 【Vue】Vuex的使用(辅助函数、模块化开发)

目录 前言 Vuex是什么 Vuex的配置 安装vuex 配置vuex文件 Vuex核心对象 actions mutations getters state Vuex在vue中的使用 辅助函数 Vuex模块化开发 前言 本文介绍一种新的用于组件传值的插件 —— vuex Vuex是什么 Vuex 是一个专为 Vue.js 应用程序开发的状态…

【ArcGIS Pro微课1000例】0053:基于SQL Server创建与启用地理数据库

之前的文章有讲述基于SQL Server创建企业级地理数据库,本文讲述在SQL Server中创建常规的关心数据库,然后在ArcGIS Pro中将其启用,转换为企业级地理数据库。 1. 在SQL Server中创建数据库** 打开SQL Server 2019,连接到数据库服务器。 展开数据库连接,在数据库上右键→新…

(四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)

一、无人机模型简介: 单个无人机三维路径规划问题及其建模_IT猿手的博客-CSDN博客 参考文献: [1]胡观凯,钟建华,李永正,黎万洪.基于IPSO-GA算法的无人机三维路径规划[J].现代电子技术,2023,46(07):115-120 二、Tiki-taka算法(TTA&#xf…

基于SSH的java记账管理系统

基于SSH的java记账管理系统 一、系统介绍二、功能展示四、其他系统实现五、获取源码 一、系统介绍 项目类型:Java EE项目 项目名称:基于SSH的记账管理系统 项目架构:B/S架构 开发语言:Java语言 前端技术:HTML、CS…

初识优先级队列与堆

1.优先级队列 由前文队列queue可知,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,在此情况下,使用队列queue显然不…

git常用命令指南

目录 一、基本命令 1、创建分支 2、切换分支 3、合并分支 4、初始化空git仓库 二、文件操作 1、创建文件 2、添加多个文件 3、查看项目的当前状态 4、修改文件 5、删除文件 6、提交项目 三、实际操作 1、创建目录 2、进入新目录 3、初始化空git仓库 4、创建文…

C++STL的string模拟实现

文章目录 前言string的成员变量成员函数构造函数拷贝构造赋值重载 模拟实现string各种接口print迭代器普通迭代器const迭代器 string比较大小push_backinsert 和 eraseinserterase reserve和resizereserveresize swapfindcout和cincoutcin 前言 今天要讲string的底层实现&…

总线(什么是南北桥?您都用过哪些总线?)

什么是总线? 计算机系统中的总线(Bus)是指计算机设备和设备之间传输信息的公共数据通道,是连接计算机硬件系统内多种设备的通信线路,它的一个重要特征是由总线上的所有设备共享,因此可以将计算机系统内的多…

python基于轻量级GhostNet模型开发构建23种常见中草药图像识别系统

轻量级识别模型在我们前面的博文中已经有过很多实践了,感兴趣的话可以自行移步阅读: 《移动端轻量级模型开发谁更胜一筹,efficientnet、mobilenetv2、mobilenetv3、ghostnet、mnasnet、shufflenetv2驾驶危险行为识别模型对比开发测试》 《基…

Vue 核心 数据监听 computed | watch

Vue 核心 数据监听 computed | watch 一、今日学习目标 1.指令补充 指令修饰符v-bind对样式增强的操作v-model应用于其他表单元素 2.computed计算属性 基础语法计算属性vs方法计算属性的完整写法成绩案例 3.watch侦听器 基础写法完整写法 4.综合案例 (演示&…

selenium 解决 id定位、class定位中,属性值带空格的解决办法

一、前置说明 selenium遇到下面这种元素&#xff1a; <th id"demo id" class"value1 value2 value3 ">1、虽然id一般不会有空格&#xff0c;但是前端错误的这种写法(如下图)&#xff0c;会造成使用id定位不到元素&#xff0c;如&#xff1a; find…