【深度思考】聊聊CGLIB动态代理原理

news2024/11/22 15:20:19

1. 简介

CGLIB的全称是:Code Generation Library。

CGLIB是一个强大的、高性能、高质量的代码生成类库,它可以在运行期扩展Java类与实现Java接口,

底层使用的是字节码处理框架ASM。

Github地址:https://github.com/cglib/cglib。

CGLIB的Maven坐标如下所示:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2. 示例

首先,新增一个类:

public class Coder {
    public void work() {
        System.out.println("认真写bug……");
    }
}

然后,自定义一个方法拦截器,实现net.sf.cglib.proxy.MethodInterceptor接口并重写intercept方法:

public class AttendanceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("上班打卡……");

        Object result = proxy.invokeSuper(obj, args);

        System.out.println("下班打卡……");

        return result;
    }
}

重点看下Object result = proxy.invokeSuper(obj, args);,该行代码最终会执行真正的目标方法,在这前后,我们可以添加一些增强逻辑。

然后,新建个测试类,看下CGLIB动态代理如何使用:

public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Coder.class);
        enhancer.setCallback(new AttendanceMethodInterceptor());
        // 创建代理对象
        Object object = enhancer.create();

        Coder coder = (Coder) object;
        coder.work();
    }
}

运行以上代码,效果如下图所示:

从运行结果可以看出,在目标方法的前后,执行了自定义的操作。

3. 原理

看下上面的测试类代码,首先是创建了一个net.sf.cglib.proxy.Enhancer对象,然后调用了setSuperclass()方法

将enhancer对象的父类设置为Coder类:

紧接着调用了setCallback()方法将enhancer对象的方法拦截器设置为自定义的AttendanceMethodInterceptor:

然后是调用enhancer对象的create()方法来生成一个代理对象。

先打印下,简单看下这个代理类的信息:

图中的com.zwwhnly.mybatisplusdemo.cglibproxy.Coder$$EnhancerByCGLIB$$8e91f654就是CGLIB生成的代理类的名称。

那么这个代理类具体是什么样子呢?

在上面的测试类代码中(Object object = enhancer.create();代码之前)添加以下一行代码:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib");

然后再次运行,会看到项目根目录下生成了一个cglib文件夹,自动生成的代理类就包含在其中:

可以看到一共生成了5个类,这里重点关注下红色标记的3个类。

先看下Coder$$EnhancerByCGLIB$$8e91f654.class,这个类就是自动生成的代理类:

可以看出Coder$$EnhancerByCGLIB$$8e91f654.class继承了Coder类(也就是说自动生成的代理类其实是被代理类的一个子类),

并且重写了Coder类的work()方法,重写后的work()方法会调用自定义的方法拦截器AttendanceMethodInterceptor里的intercept()

方法。

然后看下Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa,从名称上可以看出这个类的前半段和上面的类的名称

是一样的,后半段拼接上了$$FastClassByCGLIB$$4e5eb5aa,从功能上说,这个类是上面的代理类的索引类,重点关注下里面的

getIndex()方法和invoke()方法:

最后看下Coder$$FastClassByCGLIB$$398819d0,这个类是被代理类Coder的索引类,重点也是关注下里面的

getIndex()方法和invoke()方法:

知道了这3个类的作用后,再一步一步看下示例代码中coder.work();的调用过程,因为coder是生成的代理类的实例,所以

coder.work();首先调用的是Coder$$EnhancerByCGLIB$$8e91f654的work()方法:

这里的var10000是自定义的方法拦截器AttendanceMethodInterceptor,所以执行的是红色截图里的intercept()方法,也就是:

然后看下invokeSuper()方法:

首先执行的是init()方法,在该方法内部对fastClassInfo字段进行了赋值:

从上图可以看出,fci.f1是自动生成的Coder类的索引类Coder$$FastClassByCGLIB$$398819d0,所以fci.i1 = fci.f1.getIndex(sig1);

其实执行的是的Coder$$FastClassByCGLIB$$398819d0getIndex()方法:

fci.f2是自动生成的代理类的索引类Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

所以fci.i2 = fci.f2.getIndex(sig2);其实执行的是的Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

getIndex()方法:

看完init()方法后再回到invokeSuper()方法:

上图中的FastClassInfo fci = fastClassInfo;使用到的字段fastClassInfo在init()方法内部已经赋过值,

fci.f2其实是自动生成的代理类的索引类Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

fci.i2值是1,

所以fci.f2.invoke(fci.i2, obj, args);实际执行的是:

这里的var10000其实是自动生成的代理类Coder$$EnhancerByCGLIB$$8e91f654的实例,所以接着调用的是

代理类Coder$$EnhancerByCGLIB$$8e91f654CGLIB$work$0()方法:

这里的super指的是Coder类,所以super.work();实际执行的是Coder类的work()方法:

综上所述,coder.work();的调用顺序依次是:

代理类--->自定义方法拦截器--->代理类索引类getIndex()方法-->代理类索引类invoke()方法--->代理类--->被代理类。

4. JDK动态代理与CGLIB动态代理区别(面试常问)

了解了JDK动态代理和CGLIB动态代理的原理后,现在来比较下两者的区别,这也是面试时几乎必问的一道面试题。

  1. 使用JDK动态代理,被代理类必须要实现接口,使用CGLIB动态代理,被代理类可以不实现接口

    原因分析:

    JDK动态代理生成的代理类继承了java.lang.reflect.Proxy,因为Java是单继承的,如果不通过实现接口的形式,

    无法对类进行扩展。

    CGLIB动态代理生成的代理类实际上是被代理类的子类,所以被代理类可以不实现接口。

  2. 自动生成类的数量不同

    JDK动态代理只会生成1个代理类,一般情况下名称为:com.sun.proxy.$Proxy0

    CGLIB动态代理会生成好几个类,核心的3个分别是:

    1)代理类:被代理类的子类,名称格式为Coder$$EnhancerByCGLIB$$8e91f654,包名和被代理类包名一致。

    2)代理类的索引类:名称格式为Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

    包名和被代理类包名一致。

    3)被代理类的索引类:名称格式为Coder$$FastClassByCGLIB$$398819d0,包名和被代理类包名一致。

  3. 生成代理类技术不同

    JDK动态代理使用JDK自带的ProxyGenerator类生成字节码文件。

    CGLIB动态代理使用ASM框架生成字节码文件。

  4. 调用方式不同

    JDK动态代理:代理类--->InvocationHandler.invoke()--->被代理类方法(用到了反射)。

    CGLIB动态代理:代理类--->MethodInterceptor.intercept()--->代理类索引类getIndex()--->

    代理类索引类invoke()--->代理类--->被代理类。(直接调用)

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

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

相关文章

掌握网络设备,畅游网络世界!

网络的搭建离不开网络设备&#xff0c;物理连接&#xff0c;以及设备之间的多种协议。其中在实现网络互通时&#xff0c;最常见的网络设备是路由器和交换机。 如今在各种级别的网络随处可见各种低、中、高端的路由器、交换机&#xff0c;种类繁多&#xff0c;这些不同种类的设备…

SpringBoot3集成Redis

标签&#xff1a;Redis.Mybatis.Lock&#xff1b; 一、简介 缓存在项目开发中&#xff0c;基本上是必选组件之一&#xff0c;Redis作为一个key-value存储系统&#xff0c;具备极高的数据读写效率&#xff0c;并且支持的数据类型比较丰富&#xff0c;在业务场景中的应用非常广泛…

【图像分类】理论篇 (4)图像增强opencv实现

随机旋转 随机旋转是一种图像增强技术&#xff0c;它通过将图像以随机角度进行旋转来增加数据的多样性&#xff0c;从而帮助改善模型的鲁棒性和泛化能力。这在训练深度学习模型时尤其有用&#xff0c;可以使模型更好地适应各种角度的输入。 原图像&#xff1a; 旋转后的图像&…

海外应用ASO优化的小技巧1

根据数据显示&#xff0c;用户需要7秒的时间来决定是否下载应用程序&#xff0c;所以应用的外观是用户决策的一个关键方面。 1、了解用户 63%的应用是通过应用商店搜索找到的&#xff0c;这是查找新应用最常见的方法。然而它也表明个人正在寻找特定的程序。了解用户想要做什么…

Qt 加载 libjpeg 库出现“长跳转已经运行”错误

在我以为升级到 Qt5.15.9 后&#xff0c;运行没有什么问题时&#xff0c;问题就来了 在加载 jpeg 格式的图片时&#xff0c;出现了“长跳转已经运行”的错误 这个错误一般是由 setjmp/longjmp 函数触发的&#xff0c;出现的可能的原因有以下几种&#xff08;听听 chatgpt 的回…

存款多少可以不用上班?这组数据告诉你

手有余粮&#xff0c;心中不慌&#xff0c;兜里有钱才能大胆走四方。 最近&#xff0c;与“存款”相关的词条频繁登上热搜榜&#xff0c;比如#普通人存1万块需要多久#、 #你用多久攒到人生第一个10万元#、#月薪5000两年存了8万#、#超7成年轻人存款不足10万#等词条。 △ 截图来…

MySQL 45讲笔记(1-10讲)

1. SQL语句如何开始执行&#xff1f; MySQL分为Server和存储引擎两部分&#xff1a; Server层包含连接器、存储缓存、分析器、执行器等&#xff0c;以及所有的内置函数&#xff08;事件、日期&#xff09;等等&#xff0c;还有视图、触发器。 存储引擎是负责数据的存储和提取&a…

C语言暑假刷题冲刺篇——day1

目录 一、选择题 二、编程题 &#x1f388;个人主页&#xff1a;库库的里昂 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;C语言每日一练 ✨其他专栏&#xff1a;代码小游戏C语言初阶&#x1f91d;希望作者的文章能对你…

airflow的安装

文章目录 前言一、Airflow基本概念1.概念2. 名词 二、Airflow安装1.Airflow官网2.安装python环境3. 安装anaconda34. 创建airflow虚拟环境5. 安装Airflow1. 更改pip源2. 安装airflow3. 初始化airflow4. 查看版本5. 查看airflow安装的路径6.创建账号7. 启动airflow调度8. 启动ai…

解决hbase节点已下线,但在status中显示为dead问题

工作中需要下线4台hbase小节点&#xff0c;下线完成后使用status 命令查看,有一台为dead状态: 使用status detailed 查看&#xff0c;发现“hd-03"这台节点是dead。 检查各节点配置文件无误&#xff0c;并使用 /opt/hbase/bin/hbase-daemon.sh restart master 重启两个…

改良版自编小程序上线啦---助力孟德尔随机化(Mendelian Randomization)数据挖掘

孟德尔随机化步骤相对简单固定&#xff0c;一共就是3步&#xff0c;但是如果我们一个一个的对研究变量和结果数据进行筛选&#xff0c;也是挺费时间的&#xff0c;在上篇文章《自编R语言小程序助力孟德尔随机化(Mendelian Randomization)数据挖掘》中&#xff0c;咱们自编了一个…

【ppt密码】忘记了PPT的密码,怎么办?

PPT文件设置了保护密码&#xff0c;但是密码忘记了&#xff0c;无法打开PPT文件、无法编辑PPT文件了该怎么办&#xff1f;PPT文件的两种保护密码该如何解密&#xff1f; 首先是打开密码 网上有一种解决方法&#xff1a; 1、重新命名PPT文件&#xff0c;将其后缀改为zip格式&…

字节跳动在PGO反馈优化技术上的探索与实践

背景 随着字节跳动的业务迅速增长&#xff0c;微服务的性能优化工作显得尤为重要&#xff0c;对于头部应用来说&#xff0c;提升若干百分点的性能也能为公司节省巨大的服务器资源成本。 编译器优化是软件性能优化的一种常用方法&#xff0c;相比其它特定的性能优化方法&#…

从零开始 Spring Cloud 11:Elasticsearch II

从零开始 Spring Cloud 11&#xff1a;Elasticsearch II 图源&#xff1a;laiketui.com 在上篇文章中我们学习了 es 的基本功能&#xff0c;在本篇文章中会学习 es 的一些高级功能&#xff0c;比如&#xff1a; 聚合查询自动补全集群部署 数据聚合 类型 **聚合&#xff08…

toB 业务分析

1、 如何透彻分析B端客户的需求&#xff1f; - 知乎我在讲《如何分析客户需求》这门课时&#xff0c;经常会问学员&#xff1a;“开发客户的最大困难是什么&#xff1f;”有人说价格高不好卖&#xff0c;有人说客户需求不好把握&#xff0c;有人说客户地处偏远&#xff0c;素养…

部署WMS仓储管理系统前要知道哪些关键点

随着物流行业的快速发展&#xff0c;WMS仓储管理系统解决方案已成为企业仓储管理的重要工具。然而&#xff0c;在部署WMS仓储管理系统之前&#xff0c;企业需要了解一些关键点&#xff0c;以确保系统的成功实施和高效运行。本文将介绍部署WMS前需要知道的关键点。 1、明确目标与…

一个小时入门 EJB

前置知识 在开始学习Java EE的Enterprise JavaBeans (EJB)之前&#xff0c;以下是一些你可能需要提前了解的技术和概念&#xff1a; Java基础&#xff1a;熟悉Java的基础知识&#xff0c;包括面向对象的概念&#xff08;例如类、接口、继承和多态等&#xff09;、基本的数据结…

【从零学习python 】27. Python 函数的使用及嵌套调用

文章目录 函数的文档说明1. 基本使用2. 高级使用 函数应用&#xff1a;打印图形和数学计算目标思考&实现1参考代码1 思考&实现2参考代码2 函数的嵌套调用进阶案例 函数的文档说明 1. 基本使用 def test(a, b):"用来完成对2个数求和" # 函数第一行写一个字…

产品需求管理软件:了解常见选择和功能

产品需求管理软件是一种非常重要的工具。它可以帮助企业更好地理解客户需求&#xff0c;提高产品开发效率并降低成本。本文将介绍一些常见的产品需求管理软件及其主要功能。 “产品需求管理软件有哪些&#xff1f;比较流行的有Zoho Projects、Trello、Asana、Smartsheet等。” …

「并发编程 」

一、 为什么会有线程安全问题 1&#xff09;程序和CPU间的协作关系 CPU组成 寄存器 存储了从内存加载的数据&#xff08;从内存中将数据加载到 L1&#xff0c;L2&#xff0c;L3 缓存&#xff0c;再到寄存器&#xff09; &#xff1b;寄存器的运行速度比内存快好多个级别&…