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.hqwc.cn/news/258874.html

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

相关文章

专业的合封芯片企业,合封芯片的赋能者——宇凡微

一、引言 随着科技的快速发展,芯片封装技术已经成为电子设备制造中不可或缺的一环。其中,合封芯片以其高效、节能、小型化的特点,逐渐成为市场主流。 在这篇文章中,我们将深入了解一家专业的合封芯片企业——宇凡微,…

Proteus仿真--基于ADC0808设计的调温报警器

本文介绍基于ADC0808实现的调温报警器设计(完整仿真源文件及代码见文末链接) 温度调节使用滑动变阻器模拟实现,ADC0808采集信号并输出在LCD上面显示,报警系统是LED灯和蜂鸣器实现声光电报警 仿真图如下 仿真运行视频 Proteus仿真…

利用乔拓云SAAS系统,快速、高效搭建小程序

a-service,软件即服务)系统来搭建他们的微信小程序。SAAS系统作为一种创新的软件应用模式,将软件作为一种服务提供给用户,为用户提供了更高效、更便捷的解决方案。本文将探讨为什么越来越多的商家选择使用乔拓云这种SAAS系统搭建小…

ubuntu18.04配置cuda+cudnn+anconda+pytorch-gpu+pycharm

一、显卡驱动安装 执行nvidia-smi查看安装情况 二、cuda安装 cuda官网下载cuda_11.6.2_510.47.03_linux.run,安装执行 sudo sh cuda_11.6.2_510.47.03_linux.run提升安装项,驱动不用安装,即第一项(Driver)&#xff…

因小失大,一个普通的postman多接口顺序执行,让项目瘫痪了一天

Runner的使用 postman不仅可以单独运行某个接口,postman的 Runner模块可以运行多个接口,可以实现真正意义上的自动化接口测试 Runner的主要功能如下 按顺序调用接口,执行用例; 批量发送请求; 对接口数据进行参数化…

轻松操纵SQL:Druid解析器实践

一、背景 在BI(Business Intelligence)场景中,用户会频繁使用SQL查询语句,但在平台运作过程中,面临着权限管理、多数据源处理和表校验等多种挑战。 例如,用户可能不清楚自身是否具备对特定表(…

2023-12-08 LeetCode每日一题(出租车的最大盈利)

2023-12-08每日一题 一、题目编号 2008. 出租车的最大盈利二、题目链接 点击跳转到题目位置 三、题目描述 你驾驶出租车行驶在一条有 n 个地点的路上。这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n ,通过接乘客订单盈利。你只能沿着编号递…

PSP - 计算蛋白质复合物链间接触的残基与面积

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/134884889 在蛋白质复合物中,通过链间距离,可以计算出在接触面的残基与接触面的面积,使用 BioPython 库 与 SA…

Hugging Face 给普通用户提供了一个 2 vCPU 16GB 的免费空间

Hugging Face 给普通用户提供了一个 2 vCPU 16GB 的免费空间,并且支持部署 Gradio 构建的应用程序,非常方便,下面我们进入 https://huggingface.co/spaces/ ,点击创建空间。

【kubernetes】k3s集群搭建(正在更新……)

文章目录 一、k3s简介二、快速搭建1.控制平面2.镜像加速 Pod容器集1.创建和管理pod Deployment(部署)与ReplicaSet(副本集)滚动更新 Service命名空间YAML语法管理对象常用命令缩写YAML规范 声明式配置对象标签选择器 容器运行时接口(CRI)与镜像导入导出容器运行时接口(CRI) 金丝…

Proteus仿真--160128LCD中文显示温度与时间

本文介绍基于160128LCD的中文温度与时间显示设计(完整仿真源文件及代码见文末链接) 本文基于51单片机实现160128LCD的中文温度与时间的现实,其中万年历芯片选用DS1302时钟芯片,温度传感器选用DS18B20温度传感器 仿真图如下 仿真…

105.长度最小的子数组(力扣)|滑动窗口

代码演示 class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {int result INT_MAX; // 用于存储最小子数组的长度int sum 0; // 滑动窗口的长度int i 0; // 滑动窗口的起始位置int sumlength 0; // 当前子数…