深度解析 Spring 源码:探秘 CGLIB 代理的奥秘

news2024/9/22 19:28:06

在这里插入图片描述

文章目录

    • 一、CGLIB 代理简介
      • 1.1 CGLIB 代理的基本原理和特点
      • 1.2 分析 CGLIB 如何通过字节码技术创建代理类
    • 二、深入分析 CglibAopProxy 类的结构
      • 2.1 CglibAopProxy 类结构
      • 2.2 CglibAopProxy 类源码
    • 三、CGLIB 代理对象的创建过程
      • 3.1 配置 Enhancer 生成代理对象
      • 3.2 探讨如何通过字节码生成技术嵌入拦截器逻辑到代理类中
    • 四、CGLIB 代理链的处理
      • 4.1 拦截器的调用顺序
      • 4.2 实现拦截器具体逻辑
    • 五、实践与应用
      • 5.1 编写自定义的 CGLIB 拦截器
      • 5.2 实现对非接口类的代理和增强功能

一、CGLIB 代理简介

1.1 CGLIB 代理的基本原理和特点

CGLIB是一个强大的、高性能的代码生成库。它被广泛应用于AOP(面向切面编程)、ORM(对象关系映射)和其他一些框架中。

CGLIB代理的基本原理

  1. 创建代理类:CGLIB通过ASM字节码操作框架,在运行时动态生成目标类的子类。这个子类会继承自目标类。
  2. 方法拦截:在生成的子类中,会覆盖所有非final的方法。覆盖的方法会委托给一个用户定义的拦截器(MethodInterceptor),拦截器中包含了增强的代码。
  3. 调用流程:当调用代理类的方法时,实际上是在调用被覆盖的方法。这些方法内部会调用拦截器,拦截器再去调用原始类的相应方法。

CGLIB代理的特点

  1. 无需接口:CGLIB代理不需要目标类实现任何接口,因为它是通过继承的方式来实现代理的。
  2. 性能:CGLIB生成的代理类是目标类的子类,相比于JDK动态代理(接口代理),CGLIB代理通常有更好的性能,因为它直接调用父类的方法,减少了反射调用的开销。
  3. 灵活性:由于CGLIB代理是通过继承实现的,它无法代理final类和方法。但是,它提供了比JDK代理更高的灵活性,因为它可以代理任何类,而不受接口限制。
  4. 复杂性:CGLIB代理的实现比JDK动态代理复杂,因为它涉及到字节码生成和类加载机制。
  5. 兼容性:CGLIB代理通常与Spring框架结合使用,Spring AOP默认使用JDK动态代理,但如果目标对象没有实现接口,Spring AOP会自动切换到CGLIB代理。

1.2 分析 CGLIB 如何通过字节码技术创建代理类

CGLIB通过操纵字节码,创建出目标类的子类,并在子类中覆盖非final的方法,从而实现方法拦截和增强。

CGLIB创建代理类的基本步骤

  1. 确定目标类:首先要确定需要被代理的目标类。CGLIB代理不需要目标类实现任何接口,因为它是通过继承的方式来实现代理的。
  2. 创建Enhancer对象EnhancerCGLIB中的一个核心类,用于创建代理类。首先创建一个Enhancer实例,并设置其父类(即目标类)。
  3. 设置CallbackCallback是一个接口,用于定义代理类中覆盖方法的逻辑。通常使用MethodInterceptor接口,它允许我们在调用原始方法之前和之后插入自定义代码。将实现的Callback对象设置给Enhancer
  4. 创建代理类:调用Enhancercreate()方法,CGLIB会使用ASM字节码操作框架来动态生成一个继承自目标类的子类。这个子类会覆盖所有非final的方法,并将调用委托给Callback对象。
  5. 使用代理类create()方法返回的是一个代理类的实例,这个实例可以被当作目标类的实例来使用。当调用代理类的方法时,实际上会调用MethodInterceptor中的intercept()方法。
  6. 方法调用流程:在intercept()方法中,可以调用Method对象的invoke()方法来执行原始方法。这样,我们就可以在原始方法执行前后插入自定义的逻辑,实现方法的拦截和增强。

二、深入分析 CglibAopProxy 类的结构

2.1 CglibAopProxy 类结构

  • 成员变量
    • AdvisedSupport advised:存储了AOP配置信息的数据结构,如目标对象、切面等。
    • Callback callback:CGLIB 回调对象,负责实现代理逻辑。
  • 构造方法
    • CglibAopProxy(AdvisedSupport config):构造方法接收一个 AdvisedSupport 参数,用于设置AOP配置信息。
  • 核心方法
    • getProxy(ClassLoader classLoader):生成代理对象的核心方法,接收一个 ClassLoader 参数用于加载代理类。
    • createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks):使用 CGLIB 的 Enhancer 创建代理类,并返回代理对象的实例。
    • proxy(ClassLoader classLoader, Callback[] callbacks):创建代理类并生成代理对象的实现逻辑, 使用 Enhancer 创建代理类,并指定 Callback 对象,完成代理类的生成和实例化。
    • createEnhancer():创建 Enhancer 对象,用于生成代理类, Enhancer 是 CGLIB 中负责生成代理类的核心类。

2.2 CglibAopProxy 类源码

仅展示部分源码,其它源码会在下方解决其它问题时会出现,没有出现的,读者感兴趣可以自行去解读源码。

在这里插入图片描述

三、CGLIB 代理对象的创建过程

代理对象的创建过程: 检查是否可以使用缓存的代理对象 -> 准备 CGLIB Enhancer -> 配置 Enhancer -> 设置回调处理器(Callback) -> 生成代理类字节码 -> 创建代理对象实例 -> 将代理对象缓存起来

3.1 配置 Enhancer 生成代理对象

Enhancer 对象通过调用 create() 方法来生成代理对象。

在这里插入图片描述

createHelper() 方法用来实际创建代理对象。

在这里插入图片描述

AbstractClassGenerator 对象通过调用 create() 方法,根据给定的键值(key)创建对象实例。

在这里插入图片描述

3.2 探讨如何通过字节码生成技术嵌入拦截器逻辑到代理类中

createProxyClassAndInstance 负责创建代理类的实例,使用 CGLIB 技术创建代理对象,并将指定的拦截器回调方法应用于代理对象上。

在这里插入图片描述

四、CGLIB 代理链的处理

通过ReflectiveMethodInvocation 类了解到在 Spring 框架中如何构建和执行代理链,以及拦截器如何在拦截器链中协作,以实现对目标方法的拦截和处理。

4.1 拦截器的调用顺序

在拦截器链中如何依次执行拦截器,并通过判断和调用不同的拦截器或目标方法来实现拦截和处理逻辑。

在这里插入图片描述

invokeJoinpoint()用于执行目标方法,如果拦截器链中已经没有下一个拦截器了,或者拦截器中的某个拦截器选择不继续执行拦截器链,那么就会调用这个方法来执行目标方法。

在这里插入图片描述

4.2 实现拦截器具体逻辑

定义了方法拦截器的标准,任何实现该接口的类都可以作为 Spring AOP 中的拦截器,用于在目标方法执行前后添加额外的逻辑。

在这里插入图片描述

五、实践与应用

5.1 编写自定义的 CGLIB 拦截器

假设有一个简单的服务类 UserService,其中包含一些方法,希望能够在调用这些方法之前和之后记录日志。使用CGLIB来实现一个拦截器,记录方法调用的开始和结束时间。

  1. 服务类 UserService,模拟创建和更新用户信息。
public class UserService {

    public void createUser(String username) {
        System.out.println("Creating user: " + username);
        // 模拟创建用户的逻辑
    }

    public void updateUser(String username) {
        System.out.println("Updating user: " + username);
        // 模拟更新用户的逻辑
    }
}
  1. 自定义的CGLIB拦截器,用于记录方法调用的开始和结束时间。
/**
 * 创建 LoggingInterceptor 类,实现 MethodInterceptor 接口
 */
public class LoggingInterceptor implements MethodInterceptor {
    
    /**
     * 参数:obj 是被代理的对象实例
     *      method 是被调用的方法对象
     *      args 是方法的参数数组
     *      proxy 是用于调用父类(被代理类)方法的代理对象
     */
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 获取方法调用开始时的时间戳
        long startTime = System.currentTimeMillis();
        System.out.println("Method " + method.getName() + " start at: " + startTime);

        // 调用被代理类的原始方法,而不是代理对象的方法,以避免循环调用
        Object result = proxy.invokeSuper(obj, args);

        // 获取方法调用结束时的时间戳
        long endTime = System.currentTimeMillis();
        System.out.println("Method " + method.getName() + " end at: " + endTime);
        // 方法执行所花费的时间
        System.out.println("Method " + method.getName() + " execution time: " + (endTime - startTime) + " milliseconds");

        // 调用原始方法后的返回值
        return result;
    }

    public static void main(String[] args) {
        UserService userService = new UserService();

        // 使用CGLIB的 Enhancer 类创建了 UserService 类的代理对象,并将拦截器设置为回调方法
        Enhancer enhancer = new Enhancer();
        // 设置了要代理的目标类是 UserService
        enhancer.setSuperclass(UserService.class);
        // 指定了在方法调用时应该执行的拦截逻辑
        enhancer.setCallback(new LoggingInterceptor());

        // 创建代理对象,将会在方法调用时执行我们定义的拦截逻辑
        UserService userServiceProxy = (UserService) enhancer.create();

        // 调用代理对象的 createUser 和 updateUser 方法来触发拦截器的拦截逻辑
        userServiceProxy.createUser("John Doe");
        userServiceProxy.updateUser("Jane Smith");
    }
}

//输出结果:
Method createUser start at: 1621802728000
Creating user: John Doe
Method createUser end at: 1621802728000
Method createUser execution time: 0 milliseconds
Method updateUser start at: 1621802728000
Updating user: Jane Smith
Method updateUser end at: 1621802728000
Method updateUser execution time: 0 milliseconds

5.2 实现对非接口类的代理和增强功能

实现对非接口类的代理和增强功能通常使用 Spring AOP来实现,提供了一种便捷的方式来在方法执行前、执行后、方法抛出异常时等时机插入特定逻辑,而无需修改原始类的代码。

假设有一个订单管理系统,其中包含一个 OrderService 类,该类负责处理订单相关的业务逻辑,比如创建订单、更新订单状态等。希望在处理订单相关业务时,记录日志并统计方法执行时间。

  1. 切面类 OrderAspect。
@Aspect
@Component
public class OrderAspect {

    /**
     * 切面方法,用于实现切面的逻辑 -> 表示正在执行目标方法之前
     * 接受一个 JoinPoint 参数,连接点 -> 被增强的目标方法
     */
    @Before("execution(* com.example.service.OrderService.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before executing method: " + joinPoint.getSignature());
    }

     /**
     * 切面方法,用于实现切面的逻辑 -> 表示目标方法执行完成后
     * 接受一个 JoinPoint 参数,连接点 -> 被增强的目标方法
     */
    @After("execution(* com.example.service.OrderService.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After executing method: " + joinPoint.getSignature());
    }
}
  1. 配置类中启用 Spring AOP 功能。
/**
 * 标识这个类是一个配置类 -> 告诉 Spring 容器如何配置应用程序上下文
 * 启用了 AspectJ 自动代理 -> 告诉 Spring 在运行时生成 AOP 代理以支持 @AspectJ 切面
 * 指示 Spring 在包 com.example 及其子包中扫描组件 -> 自动发现并注册带有 @Component、@Service、@Repository 和 @Controller 注解的 bean
 */
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    
}
  1. OrderService 类。
/**
 * 调用 OrderService 类的 createOrder() 或 updateOrderStatus() 方法时,OrderAspect 切面中定义的增强逻辑会在方法执行前后生效,从而实现了对非接口类的代理和增强功能
 */
@Service
public class OrderService {

    public void createOrder() {
        // 模拟创建订单的业务逻辑
        System.out.println("Creating order...");
    }

    public void updateOrderStatus() {
        // 模拟更新订单状态的业务逻辑
        System.out.println("Updating order status...");
    }
}

// 输出结果:
Before executing method: public void com.example.service.OrderService.createOrder()
Creating order...
After executing method: public void com.example.service.OrderService.createOrder()

Before executing method: public void com.example.service.OrderService.updateOrderStatus()
Updating order status...
After executing method: public void com.example.service.OrderService.updateOrderStatus()

对乐于苦斗的人来说,苦斗不是憾事,而是乐事

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

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

相关文章

如何解包 Python 恶意可执行文件

使用 Python 编写的程序通常以源码的形式发布,也可以将所有依赖都打包到一个可执行文件中。那么如何解包 Python 恶意可执行文件呢? 打包 打包与加壳不同,打包 Python 程序的目的是创建一个可以在操作系统上独立运行的可执行文件。使用例如 …

光伏开发是用什么工具提高效率?

随着全球对可再生能源的日益重视,光伏产业作为其中的佼佼者,已经取得了长足的发展。然而,如何提高光伏开发的效率,降低成本,成为了业内关注的焦点。本文将探讨光伏开发过程中所使用的工具,以及这些工具如何…

C#多维数组不同读取方式的性能差异

背景 近来在优化一个图像显示程序,图像数据存储于一个3维数组data[x,y,z]中,三维数组为一张张图片数据的叠加而来,其中x为图片的张数,y为图片行,Z为图片的列,也就是说这个三维数组存储的为一系列图片的数据…

监管端..

文章目录 1. 登录流程2. 日志AOP 1. 登录流程 使用账号(手机号)、密码、验证码。登录就是获取token的,输入的账号密码用RSA加密(非对称) 首先输入账号密码,在发送手机验证码时候先校验账号密码有没有输入…

Python筑基之旅专栏(导航)

目录 一、Python筑基之旅专栏博文清单及链接 二、推荐阅读 一、Python筑基之旅专栏博文清单及链接 01、溯源及发展 02、变量和数据类型 03、搭建Python开发环境及库 04、两个重要函数/列表/元组 05、字符串(一) 06、字符串(二) 07、字符串(三) 08、字典 09、集合 10…

MySQL--存储引擎

一、存储引擎介绍 1.介绍 存储引擎相当于Linux的文件系统,以插件的模式存在,是作用在表的一种属性 2.MySQL中的存储引擎类型 InnoDB、MyISAM、CSV、Memory 3.InnoDB核心特性的介绍 聚簇索引、事务、MVCC多版本并发控制、行级锁、外键、AHI、主从复制特…

springboot+vue2+elementui实现时间段查询

1.前端代码 使用elementui的时间段选择器&#xff1a; <el-date-picker v-model"queryPage.itemTime" type"daterange"value-format"yyyy-MM-dd" class"filter-item" range-separator"至" start-placeholder"创建…

(2024,基于熵的激活函数动态优化,具有边界条件的最差激活函数,修正正则化 ReLU)寻找更优激活函数

A Method on Searching Better Activation Functions 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 动机 4. 方法论 4.1 问题设定 4.1.1 贝叶斯错误率和信息熵 4.1.2 激活…

关于linux磁盘告警问题

案例&#xff1a;我们在执行df命令时&#xff0c;查看到磁盘利用率很高&#xff0c;但是到相对应的目录执行du -sh *来找大文件时进行删除时&#xff0c;发现各个目录相加并不大&#xff0c;如下图&#xff1a; 使用df命令查看到根(/)目录使用到33G&#xff0c;而du命令显示只使…

IBM db2数据库初体验(有图有真相保姆级教程)

前提摘要: 直接安装的是windows 系统版本, 没有安装可视化操作数据库的工具, 直接使用windows中类似cmd窗口输入命令进行操作 1. 安装好windows版本后, 鼠标单击windows最左下角的windows图标, 找到IBM DB2文件夹--> 选择单击展开下拉款--> 单击选中DB2命令窗口, 会出现…

在没有dubbo-admin情况下如何判断zk中注册的dubbo服务是否注册成功

通常我们都是通过dubbo-admin来查看dubbo服务是否注册成功&#xff0c;那么如果没有部署dubbo-admind的情况下&#xff0c;我们如何来判断dubbo服务是否注册成功&#xff1a; 一、首先我们进入到zookeeper bin目录下使用以下指令连接到zk: ./zkCli.sh -server ip:port ip&…

强大的医院绩效考核管理系统源码,支持行业内所有绩效方案,且每步核算都可自主进行方案的新建、调整。

医院绩效考核管理系统是采用B/S架构模式设计、使用JAVA语言开发、后台使用MySql数据库进行管理的一整套计算机应用软件源码。 系统和his系统进行对接&#xff0c;按照设定周期&#xff0c;从his系统获取医院科室和医生、护士、其他人员工作量&#xff0c;对没有录入信息化系统…

从Python代码到pip包:打包Python项目

大家好&#xff0c;在软件开发的世界中&#xff0c;共享和重用代码是至关重要的。Python社区为我们提供了丰富的资源&#xff0c;使得我们能够轻松地与他人分享我们的工作&#xff0c;并从他人的工作中受益。将代码打包成pip包&#xff08;Python包管理器&#xff09;是一种常见…

函数调用时长的关键点:揭秘参数位置的秘密

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、默认参数的秘密 示例代码 二、关键字参数与位置参数的舞蹈 示例代码 总结 一、默认参…

如何编辑 PDF 中的文本

使用 PDF 格式时最常见的挑战之一是弄清楚如何编辑 PDF 文档中的现有文本。该问题不仅影响新手&#xff0c;还影响多年来处理各种文档的专业人士。 PDF 格式专为处理数字纸张而设计。它以原始形式保留所有数据&#xff0c;例如表格、图章和签名。对于需要安全可靠地分发文档的…

香橙派 Kunpeng Pro 上手初体验

香橙派 Kunpeng Pro 上手初体验 目录 香橙派 Kunpeng Pro 上手初体验1.前言2.开箱3.开发板资源介绍硬件规格参数外观规格参数4.系统环境搭建系统镜像烧录ssh连接5.简单测试6.总结 1.前言 我很荣幸能收到了来自CSDN的测评邀请&#xff0c;让我有机会对香橙派最新推出的Kunpeng …

深度学习实战-yolox训练ExDark数据集所遇到的错误合集

跳转深度学习实战-yolox训练ExDark数据集(附全过程代码,超详细教程,无坑!) 一、 训练时出现ap为零 情况1.数据集没导进去 修改exps/example/yolox_voc/yolox_voc_s.py 当然由于image_sets只有一个元素因此修改yolox/data/datasets/voc.py 情况2.iou设置过高 修改yolo…

InteractiveGraph图谱中vue项目中如何使用

InteractiveGraph图谱中vue项目中如何使用 一、下载js和css和字体二、vue2.0项目中引用三、grap组件 一、下载js和css和字体 //在这里面找 https://github.com/grapheco/InteractiveGraph/blob/master/dist/examples/example1.html二、vue2.0项目中引用 //main.js中全局引入$ …

Pytorch深度学习实践笔记8(b站刘二大人)

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;pytorch深度学习 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 《PyTorch深度学习实践》完结合集_哔哩哔哩_bilibi…

新窃密软件 NodeStealer 可以窃取所有浏览器 Cookie

Netskope 的研究人员正在跟踪一个使用恶意 Python 脚本窃取 Facebook 用户凭据与浏览器数据的攻击行动。攻击针对 Facebook 企业账户&#xff0c;包含虚假 Facebook 消息并带有恶意文件。攻击的受害者主要集中在南欧与北美&#xff0c;以制造业和技术服务行业为主。 2023 年 1 …