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


    1. 简介

    CGLIB的全称是:Code Generation Library。

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

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

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

    CGLIB的Maven坐标如下所示:

    <dependency>
        <groupId>cglibgroupId>
        <artifactId>cglibartifactId>
        <version>3.3.0version>
    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动态代理,可以查看上一篇博客:【深度思考】聊聊JDK动态代理原理

    了解了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()--->代理类--->被代理类。(直接调用)

    文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!

  • 相关阅读:
    python基础知识整理 09-多任务:协程
    英伟达开源 3400 亿参数模型;苹果 iOS 18 紧急 SOS 新增实时视频功能丨 RTE 开发者日报 Vol.225
    dpdk-19.11 中 simd 指令使用现状分析
    【FFmpeg】av_write_frame函数
    dreamweaver网页设计作业制作 学生个人网页单页 WEB静态网页作业模板 大学生个人主页博客网页代码 dw个人网页作业成品简单页面
    【DL】使用pytorch从零实现线性回归
    比selenium体验更好的ui自动化测试工具: cypress介绍
    武汉新时标文化传媒抖音小店官网功能大揭秘
    【案例回顾】春节一次较波折的MySQL调优
    Yield Guild Games:社区更新——2022 年第 3 季度
  • 原文地址:https://www.cnblogs.com/zwwhnly/p/17340026.html