聊聊cglib动态代理遇到的坑

2023-11-06

简介

cglib是另外一种动态代理的方法,他和jdk动态代理的实现是有区别的,我们在之前见过jdk动态代理类是必须实现了接口的,而cglib不需要实现接口,但是必须保证类不含有final关键字,否则是无法代理的。 本文是从个人不小心遇到的cglib的死循环问题从而展开的分析。

cglib案例

下面我们来展示一个cglib的死循环案例。首先是要被代理的类,还是和常规的一样,声明自己的方法就行,但是要确保类和方法没有被final关键字修饰。用final关键字修饰类会直接报异常,但是修饰方法不会抛异常,但是此方法不会被代理,但是不影响其他方法被代理。

public class InfoDemo {
    public void welcome (String person){
        System.out.println("welcome :" + person);
    }
}
复制代码

下面是具体的代理类实现

public class CglibInfoProxy implements MethodInterceptor{
    private Object target;
    public Object newInstance(Object source){
        target = source;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before method!!!");
        Object value = methodProxy.invoke(o, objects);
        //Object value = methodProxy.invoke(this.target, objects);
        //Object value = methodProxy.invokeSuper(o, objects);
        return value;
    }
   public static void main(String[] args) {
        //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\\\classes");
      InfoDemo instance = (InfoDemo) new CglibInfoProxy().newInstance(new InfoDemo());
      instance.welcome("zhangsan");

    }
}
复制代码

和我们的jdk动态代理看起来十分相似,只是两个类实现的接口不同,并且生成对象的方法也不同。这里非常坑的是invoke方法和invokeSuper的区别,如果是用invoke方法一定要使用被代理的对象也就是上文中的target,而如果调用invokeSuper方法,则一定要使用被代理后的o对象。

上述这个例子就会引发死循环,导致StackOverflowFlow,嘿嘿,学没有,栈溢出的场景

具体为什么会这样,可以先思考一下,后面我们在源码实现中再去讲解。现在我们先看一下运行结果

...
before method!!!
before method!!!
before method!!!
Exception in thread "main" java.lang.StackOverflowError
	at java.nio.CharBuffer.<init>(CharBuffer.java:281)
	at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:70)
	at java.nio.CharBuffer.wrap(CharBuffer.java:373)
	at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
	at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
	at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
	at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
	at java.io.PrintStream.write(PrintStream.java:526)
	at java.io.PrintStream.print(PrintStream.java:669)
	at java.io.PrintStream.println(PrintStream.java:806)
	at com.eumji.proxy.cglib.CglibInfoProxy.intercept(CglibInfoProxy.java:30)
	at com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$870a84d7.welcome(<generated>)
	at com.eumji.proxy.cglib.InfoDemo$$FastClassByCGLIB$$2e560a7d.invoke(<generated>)
	at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
...
复制代码

此处只展示部分的效果,具体可以自己试一下。

假如我们换成其余他两条语句将会是正确的输出,具体结果如下

before method!!!
welcome :zhangsan
复制代码

原理解析

要想弄清楚的这到底是怎么回事,首先我们要看一下cglib代理后的类是怎样的,要想生成代理类的文件,我们只需要在我们的main方法中取消掉这句方法的注释

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");

就会在D盘的classes文件下生成对应的代理class文件,需要注意的是生成的代理class是有三个的,我们首先介绍一下我们最关心的InfoDemo代理类,其他的稍后合适的时机在描述其他两个。

InfoDemo反编译代码

其实就是将class文件直接拖到IDEA中

public class InfoDemo$$EnhancerByCGLIB$$870a84d7 extends InfoDemo implements Factory {
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$welcome$0$Method;
    private static final MethodProxy CGLIB$welcome$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$870a84d7");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.eumji.proxy.cglib.InfoDemo")).getDeclaredMethods())[0];
        CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
    }

    final void CGLIB$welcome$0(String var1) {
        super.welcome(var1);
    }

    public final void welcome(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
        } else {
            super.welcome(var1);
        }
    }
  ...

}

复制代码

看代码就可以看出来cglib还是很复杂的,现在我们暂且可以看一下我们关心的welcome方法,从上面的代码中可以看到cglib是会为被代理类的方法同时生成两个代理方法的,一个是同名的welcome方法CGLIB$welcome$0方法

1.CGLIB$welcome$0方法直接调用被代理的方法,也就是啥都没干。

2.welcome方法首先判断有没有设置callback,很明显我们在代码中有设置即为CglibInfoProxy,所以就会调用CglibInfoProxy.intercept方法。

本来想分析一波生成代理类的过程,看了一下有点复杂,暂时就不分析了。。。。

invokeSuper方法

前面也提及了invoke和invokeSuper方法稍不注意就会出问题的问题,在这里我们从代码的层面去追踪一下,产生问题的原因。

我们看一下代理类方法invokeSuper的执行流程

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init(); //初始化fastInfo
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}
复制代码

invokeSuper在这里主要的作用就是初始化fastClassInfo。

init方法

private void init() {
    if (this.fastClassInfo == null) {
        Object var1 = this.initLock;
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}
复制代码

上面的方法主要是加载methodProxy.FastClassInfo。ci是之前就初始化好的,其中c1指的就是被代理的类InfoDemo,c2则是com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$efe38465这个代理类。

然后生成对应的f1和f2以及方法的下标i1和i2,i1和i2对应的就是在最前面所说的welcome方法CGLIB$welcome$0方法,后面代码可以看出。

而f1则是对应InfoDemo$$FastClassByCGLIB$$2e560a7d代理类,f2则对应InfoDemo$$EnhancerByCGLIB$$efe38465$$FastClassByCGLIB$$38345933代理类。这些都可以在生成的代理class中去查看。

当然这里并没有说到底什么生成的,有兴趣的可以自己看一下字节码是怎么生成的。个人没太看懂。

invoke和invokeSuper区别

为什么要生成两个代理类f1和f2,我相信你看过之前的方法应该注意到了,在上面我们提及到了invoke方法和invokeSuper方法,我们来对比一下invoke方法和invokeSuper方法的区别

public Object invoke(Object obj, Object[] args) throws Throwable {
  this.init();
  MethodProxy.FastClassInfo fci = this.fastClassInfo;
  return fci.f1.invoke(fci.i1, obj, args);
}
复制代码

我们可以看到invoke使用的是f1.invoke方法,而invokeSuper则是使用f2.invoke方法。

首先看一下f1对应的invoke方法逻辑

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        InfoDemo var10000 = (InfoDemo)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.welcome((String)var3[0]);
                return null;
                ....
}
复制代码

直接调用InfoDemo对象的welcome方法。

所以这也就能解释为什么我们之前会发生循环调用invoke的方法了,因为我们传入的var2是InfoDemo的代理对象,看最前面的代理类代码就可以看出,又会回到invoke方法,造成死循环。

再看一下f2中对应invoke的实现

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        efe38465 var10000 = (efe38465)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
             ....
                var10000.CGLIB$finalize$1();
                return null;
            case 16:
                var10000.CGLIB$welcome$0((String)var3[0]);
                return null;
           ....
    }
复制代码

因为我们此时我们传入的var2是InfoDemo代理对象,所以最终会调用代理类中的CGLIB$welcome$0方法。

小结

只是一次失败的源码分析尝试,不过弄清楚了造成调用死循环的原因,只能说cglib比jdk的动态代理复杂很多,主要体现在生成代码的逻辑和生成的代码上,还有待深入的学习。

而且是有两种invoke方法即invoke和invokeSuper方法,所以使用的时候必须要谨慎。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

聊聊cglib动态代理遇到的坑 的相关文章

  • 继续使用 sketch.js 编辑草图图像 [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我正在使用 sketch js 中的示例 http intridea github io sketch js http intridea g
  • Powermockito 可以在非最终具体类中模拟最终方法吗?

    假设我有一个非最终具体类 具有如下所示的最终方法 public class ABC public final String myMethod return test test 可以嘲笑吗myMethod 调用时返回其他内容junit usi
  • jUnit 中每个 @Test 的不同拆卸

    有没有办法为 jUnit 中的每个 Test 定义不同的拆卸 Use the After注释来指示每个之后要运行的方法 Test 像这样的全套注释是 BeforeClass 首先 Tests are run Before 在每个之前 Tes
  • CXF 客户端异常:{XXX} 的拦截器已引发异常,现在展开

    我遇到以下 CXF 异常 warning Interceptor for http example com wsdl esc 2011 12 12 AmazonEC2 http example com wsdl esc 2011 12 12
  • 通过代理从java发送电子邮件

    我使用 Java Mail API 来发送和接收电子邮件 现在我做这个项目的地方有一个代理服务器 我可以知道如何通过代理服务器从java发送电子邮件吗 请参阅此处的常见问题解答 http www oracle com technetwork
  • Java 将 String[] 转换为 int[]

    我有一个 String 其中每个元素都可以转换为整数 将其转换为 int 的最佳方法是什么 int StringArrayToIntArray String s public static int StringArrToIntArr Str
  • GWT 代码服务器在使用 Maven 原型的新生成的项目中找不到模块

    我已经使用 GWT 和 eclipse 一段时间了 我想玩一下 Maven 和 GWT 插件 gwt maven plugin 在此输入链接描述 http mojo codehaus org gwt maven plugin 我尝试在 Ec
  • Android - 使用 Intent 打开 PDF 文档关闭后不保存

    我面临的问题是 当尝试保存对使用此 URI 打开的 PDF 文档的更改时内容 xx xxx xxx fileprovider external Download Sync FileName pdf 我所做的任何更改在关闭文档后都不会保存 但
  • 如何将选定的项目移动到列表顶部

    List
  • Hazelcast Jet 变更数据捕获

    我在我的应用程序中使用 Hazelcast 更改数据捕获 CDC 我使用 CDC 的原因是 如果使用 jdbc 或其他替代功能将数据加载到缓存中 会花费大量时间 所以CDC将在数据库和 Hazelcast Jet 之间进行数据同步 Stre
  • 查找前 N 个五边形数

    我必须找到第一个N pentagonal numbers 1 从 1 100 并每行显示 10 个 我必须使用getPentagonalNumber int n 方法也是如此 显然这就是它存在的原因 到目前为止 这是我的代码 package
  • 使用 Gradle 构建 Kotlin + Java 9 项目

    我对 Gradle 老实说 还有 Java 9 相当陌生 我正在尝试使用 Gradle 构建一个混合了 Java 9 和 Kotlin 的简单库项目 更详细地说 Java中有一个接口 Kotlin中有一个实现 我会用 Kotlin 做所有事
  • 没有字符串参数构造函数/工厂方法可以从字符串值 ('') 反序列化

    我在使用时遇到了 json 解析问题ObjectMapper类来自com fasterxml jackson databind包 我得到的错误是 com fasterxml jackson databind JsonMappingExcep
  • Java 会话变量

    我听说有些人认为在会话中将信息存储在服务器上是一个坏主意 因为它不安全 因此 在多页面业务流程功能中 应用程序将数据写入数据库 然后在需要时检索信息 在会话中存储私人信息是否一定不安全 只要会话本身安全 在会话中存储属性就不存在安全风险劫持
  • java代码的等效vb代码

    谁能告诉我这段Java代码到底做了什么 SecureRandom random SecureRandom getInstance SHA1PRNG byte bytes new byte 20 synchronized random ran
  • 使用 spring mvc 的多个域

    假设我有一个应用程序必须缩短 URL 但还要执行其他操作 喜欢google com and goo gl or facebook com and fb me 部署两个应用程序很容易 但 目前 仅部署一个应用程序更简单 使用 spring 和
  • 选择活动时运行时崩溃

    首先我想说我几乎没有 Android 经验 这是我在 Android 中的第一个项目 而且我的老师不太擅长教学 所以我对任何过度的无知表示歉意 在进一步讨论之前先解释一下 我的应用程序的目标本质上是能够记录您在某些活动上花费了多少时间 记录
  • 为什么我得到:没有有效的 JFX 运行时

    我有一个使用 java 1 6 编译并使用 jnlp webstart 运行的现有应用程序 如果我使用 JRE 1 6 从客户端运行此应用程序 一切都会很好 但是 当我使用 java JDK 7 编译代码并使用 JRE 1 7 67 运行客
  • 如何设置 intellij 在日志选项卡而不是输出选项卡中显示日志

    当我使用 intellijs 12 1 4 内置 tomcat 时 日志将被写入调试面板的输出选项卡 而不是正常的本地主机日志选项卡 我跟着this http www jetbrains com idea webhelp run debug
  • 这种说法是否恰当。 if (0 != 表达式或变量) {} 在java中? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi

随机推荐

  • Hugging Face——MLM预训练掩码语言模型方法

    对于许多涉及 Transformer 模型的 NLP 程序 我们可以简单地从 Hugging Face Hub 中获取一个预训练的模型 然后直接在你的数据上对其进行微调 以完成手头的任务 只要用于预训练的语料库与用于微调的语料库没有太大区别
  • PHP CGI、FastCGI、PHP-FPM、PHP-CGI 区别

    PHP CGI FastCGI PHP FPM PHP CGI是不同的PHP执行方式和处理程序 它们有以下区别 PHP CGI Common Gateway Interface PHP CGI是一种通过CGI协议与Web服务器通信的PHP执
  • 基于 LSTM 的船舶轨迹预测,单步预测

    之前给的数据和代码可能有一些问题 现在从新修改一下 末尾提供数据集和源码链接 单步预测步长 10 单步循环预测长时间的位置 从第1个位置开始 前10个位置 真实位置 预测第11个位置 然后第2个位置到第11个位置 预测值 为一组 预测第12
  • c++20新特性

    个人博客地址 https cxx001 gitee io 前言 自C 11这个大版本更新以来 后来陆续有两次小版本迭代C 14 C 17 它们主要是对C 11的补充扩展 并没有增加太多大的特性 而这次的C 20 和当年C 11一样 又是一次
  • 用卷积神经网络实现表情识别

    用卷积神经网络实现表情识别 一 卷积神经网络简介 1 定义 2 结构 3 理论 4 性质 二 在python环境下实现 三 总结 一 卷积神经网络简介 1 定义 卷积神经网络 Convolutional Neural Networks CN
  • 吴恩达机器学习python作业之单变量线性回归

    第一个方法读取数据用的是pandas 第二个方法读数据用的是numpy 第一种方法是梯度下降法 第二种方法是正规方程法 跟着佬们的思路写写改改 如果有错误请私信或评论哦 数据集理解 ex1data1 txt的数据集是两列 第一列是popul
  • 炫酷的开关--20230907

    Night Day Toggle Completed It HTML div class controls div
  • Vue3 setup语法糖使用简易教程(下)

    Vue3 setup语法糖使用简易教程 下 前几天一个月薪35k的兄弟 给我推了一个人工智能学习网站 看了一段时间挺有意思的 包括语音识别 机器翻译等从基础到实战都有 很详细 分享给大家 1 组件 1 1组件引用 组件在props里直接引入
  • python configparser读取.conf配置文件获得配置信息

    configparser是用来读取配置文件的包 配置文件的格式如下 中括号 内包含的为system 下面为key value 的配置内容 web conf内容 1 py 内容 导入模块 import os configparser 读取文件
  • 今日头条号问答微头条原创收益向百粉开放

    今日头条号 问答和微头条原创收益向百粉作者开放了 以前没有还可以开通的作者 这次可以抓紧时间去申请开通 作者宋九九 头条号发布公告称 自2021 年 1 月 13 日起 微头条创作收益和问答创作收益权益面向粉丝数不低于100作者开放 只要你
  • Spring配置文件报错 application context not configured for this file

    Spring配置文件报错 application context not configured for this file 解决方法一 点击Configure application context 选择Create new applica
  • FileZilla Server 下载、安装、配置教程

    下载filezilla server filezilla server官网 FileZilla The free FTP solution https filezilla project org FileZilla Server下载 Fil
  • Python模块导入时全局变量"__all__"的作用

    Python中一个py文件就是一个模块 all 变量是一个特殊的变量 可以在py文件中 也可以在包的 init py中出现 1 在普通模块中使用时 表示一个模块中允许哪些属性可以被导入到别的模块中 如 全局变量 函数 类 如下 test1
  • 抓住czx【最短路】

    题目链接 首先 做这样的处理 把每个点的时间分割为几个区间 说明在这个区间内的时候 人在这个点内 那么 我们就有这样的选择 如果在这个区间内 或者区间之前抵达 就说明是可以碰见的 如果在这个区间之后抵达 就说明是见不到的了 所以跑最短路 如
  • Spring IOC容器初始化过程及其原理(源码层面)

    Spring大家族在Java技术生态体系中占有重要地位 其中Spring更是其中的佼佼者 它极大的简化了我们的代码开发量 提高我们的工作效率 其中Spring两大特性中的IOC特性是至关重要的 今天来从底层看一看Spring的容器的初始化过
  • USB描述符 包括bushound抓包

    USB描述符 USB描述符信息存储在USB设备中 在枚举过程中 USB主机会向USB设备发送GetDescriptor请求 USB设备在收到这个请求之后 会将USB描述符信息返回给USB主机 USB主机分析返回来的数据 判断出该设备是哪一种
  • 如何理解等错误率(EER, Equal Error Rate)?

    在语音vad和KWS任务中 经常用到EER 怎么正确理解EER FR定义 在一批本该全部正确 TRUE 的列表中出现几个没识别出正确的语音 这个就是错误拒识FR False Rejection 是Miss的 FA定义 在一批本该全部错误 F
  • 6、一个简单的新氧的小爬虫

    from bs4 import BeautifulSoup import requests import math url hos for i in range 1 15 url source http y soyoung com hosp
  • 输出二叉树的所有路径

    给你一个二叉树的根节点 root 按 任意顺序 返回所有从根节点到叶子节点的路径 叶子节点 是指没有子节点的节点 输入 root 1 2 3 null 5 输出 1 gt 2 gt 5 1 gt 3 解法一 深度优先搜索 递归 迭代也可以实
  • 聊聊cglib动态代理遇到的坑

    简介 cglib是另外一种动态代理的方法 他和jdk动态代理的实现是有区别的 我们在之前见过jdk动态代理类是必须实现了接口的 而cglib不需要实现接口 但是必须保证类不含有final关键字 否则是无法代理的 本文是从个人不小心遇到的cg