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

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 {@Overridepublic 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.hqwc.cn/news/67727.html

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

相关文章

虚幻5中Lumen提供哪些功能以及如何工作的

虚幻引擎 5 中的 Lumen 是一个完全动态的全局照明和反射系统。它可以在虚幻引擎 5 中使用&#xff0c;因此创作者无需自行设置。它是为下一代控制台和建筑可视化等高端可视化而设计的。那么它提供了哪些功能以及如何工作&#xff1f; 全局照明 当光离开光源时&#xff0c;它会…

Android Studio实现列表展示图片

效果&#xff1a; MainActivity 类 package com.example.tabulation;import android.content.Intent; import android.os.Bundle; import android.view.View;import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; im…

Github上git lfs oid sha256文件无法下载的解决方案

问题&#xff1a;github上sha文件无法下载&文件超出限制 当我克隆Github上的一个库时&#xff0c;其中有一个包的内容格式如下&#xff1a; version https://git-lfs.github.com/spec/v1 oid sha256:一堆数字和字母 size 一堆数字 这堆东西类似百度网盘的下载链接和密码&a…

React(5)

1.受控组件案例 1.1之前的影院案例改写 import React, { Component } from react import axios from axios import BetterScroll from better-scroll import ./css/02_tab.cssexport default class Cinema extends Component {constructor() {super();this.state {cinemaLis…

通过 Amazon SageMaker JumpStart 部署 Llama 2 快速构建专属 LLM 应用

来自 Meta 的 Llama 2 基础模型现已在 Amazon SageMaker JumpStart 中提供。我们可以通过使用 Amazon SageMaker JumpStart 快速部署 Llama 2 模型&#xff0c;并且结合开源 UI 工具 Gradio 打造专属 LLM 应用。 Llama 2 简介 Llama 2 是使用优化的 Transformer 架构的自回归语…

怎么把CAD转成JPG图片?一个方法教你如何转换

CAD是计算机辅助设计的缩写&#xff0c;通常指的是AutoCAD软件所生成的DWG文件格式。DWG是一种二进制文件格式&#xff0c;用于保存2D和3D设计数据和元数据&#xff0c;支持多种操作系统和程序使用。该格式通常用于工程、建筑、制造和其他领域的设计和绘图。 CAD文件转换成JPG图…

【Rust】Rust学习 第十一章编写自动化测试

Rust 是一个相当注重正确性的编程语言&#xff0c;不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫&#xff0c;不过它不可能捕获所有种类的错误。为此&#xff0c;Rust 也在语言本身包含了编写软件测试的支持。 编写一个叫做 add_two 的将传递…

Debian 10驱动Broadcom 无线网卡

用lspci命令查询无线网卡品牌&#xff1a; 运行下面代码后&#xff0c;重启即可。 apt-get install linux-image-$(uname -r|sed s,[^-]*-[^-]*-,,) linux-headers-$(uname -r|sed s,[^-]*-[^-]*-,,) broadcom-sta-dkms

/proc/net/dev 最后一行读2次

网络2倍字节量和网速&#xff0c;百思不得其解。 void netdev(SamplePlugin *sample) {FILE *fp;char s[150], itf[10];long long r1, r2, r3, r4, r5, r6, r7, r8, t1, t2, t3, t4, t5, t6, t7, t8;int i0;sample->rb 0;sample->tb 0;fp fopen("/proc/net/dev&…

一般股票量化交易接口需要用到哪些编程语言?

股票量化交易接口可以使用多种编程语言进行开发和调用。以下是一些常用的编程语言&#xff1a; 1. Python&#xff1a;Python是一种脚本语言&#xff0c;广泛应用于量化交易领域。它有丰富的第三方库和工具&#xff0c;如Pandas、NumPy和pyalgotrade等&#xff0c;可用于数据分…

Makefile多个子文件夹

首先&#xff0c;目录结构&#xff1a; 其中根目录Makefile主要作用是调用其他子文件夹Makefile&#xff0c;每个子模块执行各自编译后在build文件夹下生成obj文件&#xff0c;最后再执行build文件夹下Makefile进行链接。 根目录Makefile&#xff1a; TARGET ACT_Drv ##SRC_D…

【STM32】利用CubeMX对FreeRTOS用按键控制任务

对于FreeRTOS中的操作&#xff0c;最常用的就是创建、删除、暂停和恢复任务。 此次实验目标&#xff1a; 1.创建任务一&#xff1a;LED1每间隔1秒闪烁一次&#xff0c;并通过串口打印 2.创建任务二&#xff1a;LED2每间隔0.5秒闪烁一次&#xff0c;并通过串口打印 3.创建任…