CGLIB原理简析-前篇

CGLIB(Code Generation Library)是实现动态代理的一种方案。动态代理的内容一般都包含三个部分:① 代理类的生成;② 代理类的实例化;③ 代理类的使用。 本文作为CGLIB文章的前篇,将通过与使用者直接相关入手,先介绍代理类使用相关底层逻辑。即,当我们调用代理对象的对应方法时如何实现代理的。关于前两个部分将会在后篇进行介绍。

一、简单使用

开篇第一章,先回顾下CGLIB动态代理的简单使用,代码如下:
① 被代理类

public class Student {
    public String name;

    public Student() {
        this.name = "spl";
    }

    public void outStudentName() {
        System.out.println(this.name);
    }
}

② 方法拦截器

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("代理前置....");
        System.out.println("第一个参数:obj = " + obj.getClass().getSimpleName());
        System.out.println("第二个参数:method = " + method.getName());
//        System.out.println("第三个参数:args = " + args);
        System.out.println("第三个参数:proxy = " + proxy.getSignature());
        Object invokeResult = proxy.invokeSuper(obj, args);
        System.out.println("代理后置....");
        return invokeResult;
    }
}

③ 使用cglib代理

public class Client {
    public static void main(String[] args) {
        System.setProperty ( DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/spl/own/mavenTest" );

        MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Student.class);
        enhancer.setCallback(myMethodInterceptor);
        Student stu = (Student) enhancer.create();
        stu.outStudentName();
    }
}

执行结果:
在这里插入图片描述
最上面的红字表示我已经开启了保存动态类信息功能,便于后续分析代理功能的实现逻辑。下面输出了多行信息表明动态代理已生效。

二、增强代理类结构

配置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY属性,cglib会将动态生成的类保存在指定目录下。
在这里插入图片描述
可以看到,动态生成代理类文件路径在指定目录下的代理类同路径下,即${DebuggingClassWriter.DEBUG_LOCATION_PROPERTY}/{被代理类包路径}.

net.sf.cglib包下的动态代理类先不介绍,先看Strudent$$XXX动态代理相关类。

  • Student$$EnhancerByCGLIB$86327ae0
    生成并写入文件的增强代理类。
  • Student$$EnhancerByCGLIB$2838b21a$FastClassByCGLIB$594c36f
    前缀和增强代理类相同,后面是FastClass类的标志,因此该文件是调用代理方法后生成的代理类的FastClass子类。
    FastClass是为了避免反射获取类方法而设计的,内部维护索引到各个方法的映射,加速了CGLIB代理方法的执行速度。
  • Student$$FastClassByCGLIB$53ab8f74
    如上规律,该文件时调用代理方法后生成的被代理类的FastClass子类。功能如上。

    FastClass子类及其对象都是在调用对应代理方法时才会被创建、实例化。这部分后续文中会提到。

2.1 代理类类间关系

public class Student$$EnhancerByCGLIB$$2838b21a 
extends Student 
implements Factory {
...
}

代理类直接继承了被代理类,而且实现了Factory接口。所有被Enhancer类返回的增强代理类均会实现Factory接口。Factory接口提供了一系列创建新增强代理对象的newInstance()方法,且支持替换之前指定的Callbacks列表(什么是Callback?简单理解为方法回调对象,如上文示例中MyMethodInterceptor实现的MethodInterceptor接口就继承了Callbacks接口)。

【TODO】cglib能够代理抽象类、接口吗?不能。

2.2 增强代理类属性

增强代理类的属性如下:

  • 2个对象属性(非静态)
    • private boolean CGLIB$BOUND;
      标识当前增强代理对象是否已绑定回调对象(Callback)。增强代理类支持修改回调对象,且具备有限线程上下文中的回调对象,因此当调用任何公开方法时,都会根据此表示判断回调对象是否已绑定在下面这个回调对象属性上。
    • private MethodInterceptor CGLIB$CALLBACK_{index};
      回调对象。允许有多个,这个属性指向了第index个回调对象。
  • 5个静态类成员属性
    • public static Object CGLIB$FACTORY_DATA;
      用途暂不清楚【TODO】
    • private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
      线程级缓存回调对象列表,优先级高于全局
    • private static final Callback[] CGLIB$STATIC_CALLBACKS;
      通过内部公开Set方法设置全局回调对象列表
    • private static Object CGLIB$CALLBACK_FILTER;
      用途暂不清楚【TODO】
    • private static final Object[] CGLIB$emptyArgs;
      当方法参数为空时,会传递该空数组给回调方法(intecept)上。
  • (n+4)组类方法相关句柄
    • n为被代理对象方法个数,每组包含的方法如下:
      private static final Method CGLIB$outStudentName$0$Method;           // 被代理方法句柄
      private static final MethodProxy CGLIB$outStudentName$0$Proxy;		// 代理方法句柄
      
      被代理方法对象以及代理方法对象会作为第二、第四个参数传给回调函数的intercep(…)方法。
    • cglib会为每一个需要被代理的方法创建一组方法句柄。除了被代理类方法之外,还有Object类的equals()、toString()、hashCode()以及clone()也被代理。

2.3 增强代理类静态初始化

JVM加载代理类时会初始化所有的被代理方法对象(Method)和代理方法对象(MethodProxy)。
被代理方法Method对象的获取是通过反射的方式获取,而代理方法的获取是创建MethodProxy对象,MethodProxy创建时会持有被代理类Class对象、代理类Class对象、被代理方法名、代理方法名、以及方法描述(由参数类型即返回值组成)。
在具体调用方法拦截器的intercept方法入参中Method就是这里的被代理方法、MethodProxy就是代理方法对象。MethodProxy在整个CGLIB代理过程中作用至关重要,具体有关MethodProxy细节在后续第三章内容。

2.4 增强代理类构造函数

public Student$$EnhancerByCGLIB$$86327ae0() {
    CGLIB$BIND_CALLBACKS(this);
}

代理类仅提供了一个无参构造方法,该方法内部仅做了一件事情-给代理类对象绑定回调方法。绑定回调方法的过程:

  1. 根据属性CGLIB$BOUND,如果已经绑定,就不再绑定了。否则2;【绑定回调方法会在多处用到,因此这里专门使用一个属性标识当前对象是否已绑定】
  2. 设置属性CGLIB$BOUND为true,标识当前对象已绑定
  3. 优先通过当前静态线程上下文(CGLIB$THREAD_CALLBACKS)中获取回调对象,如果不存在则4,存在则5;
  4. 线程上下文不存在,就从当前对象的CGLIB$STATIC_CALLBACKS静态属性获取,如果不存在返回,存在则5;
  5. 将获取的多个回调对象(可能有多个)设置到**CGLIB$CALLBACK_{index}**属性中。

构造方法还有一件事情,就是调用父类的构造方法。如果被代理对象时有参构造,代理对象也是有参构造,并会显示调用super(…)方法

静态初始化+构造方法基本上是将代理类所必须的属性全部设置完。其中静态初始化的属性均为所有对象确定且一致的,而构造函数依赖于运行时可变静态属性。

2.5 增强代理类公开方法

代理类的公开方法在这里会被分为三个部分,分别是父类引用方法、接口引用方法、内部公开方法。我先说下为什么这么分:既然是代理类,那么该类的类名实际上肯定是运行时动态确定的,因此在实际程序代码中不太可能使用代理类型变量,因此代理类对象肯定是通过多态进行调用
根据代理类的类间关系,代理类对象要么被其父类(即被代理类)引用,要么被其实现的接口(Factory)引用。除此之外,代理类还有几个内部静态公开方法,这个是给内部使用的,上层应用程序中使用不到。

2.5.1 代理类重写父类方法执行过程

前面说到增强代理类代理的方法有(n+4)个,其中n为被代理类的方法,4为Object类中的方法。这些方法在代理类中会被重写,代理对象调用时的逻辑可总结图如下:
在这里插入图片描述
流程还是十分简单清楚的,有回调对象就直接调用其intercept()方法,具体后续交给每一个MethodInteceptor对象处理。反之,如果没有回调对象,那就只能调用父类方法了(即,无代理动作)。
这里说一下调用MethodInteceptor#intercept()方法的入参,为后续做好铺垫。几乎所有重写方法的内部逻辑差异不大,这里以Object#equals方法为例:

Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);

var10000就是MethodInteceptor回调对象,intercept方法的第一个参数就是当前代理类对象,第二个参数被代理方法(Method),第三个参数是方法入参,第四个参数是代理方法(MethodProxy)。如果方法没有任何入参,第三个参数会传入CGLIB$emptyArgs空Object数组。

2.5.2 Factory方法

前面已经提到所有创建的增强代理类都会实现Factory接口,Factory接口提供了两个能力:

  1. 重新创建相同的代理对象
    • Object newInstance(Callback callback);
      无参创建对象并传入一个回调函数
    • Object newInstance(Callback[] callbacks);
      无参创建对象并传入多个回调函数
    • Object newInstance(Class[] types, Object[] args, Callback[] callbacks);
      有参创建多个对象并传入多个回调函数
      实际上这三种方法差异不大,均是调用对应代理类的构造方法,设置回调函数方法依靠CGLIB$THREAD_CALLBACKS线程上下文属性。但需要注意的是,回调对象的个数不能比之前少,多余部分会忽略。
  2. 设置或获取回调对象
    • Callback[] getCallbacks();
      返回所有回调对象列表
    • Callback getCallback(int index);
      获取第index个回调对象,越界返回null
    • void setCallback(int index, Callback callback);
      替换第index个回调对象;注意不能新增回调对象个数
    • void setCallbacks(Callback[] callbacks);
      替换所有的回调对象;注意回调对象个数和之前保持一致。

    这里使用setXXX方法不好,其实内部逻辑实际上是replaceXXX。

2.5.3 内部静态公开方法

增强代理类除了以上公开方法,还有三个内部使用的静态方法:

  1. MethodProxy CGLIB$findMethodProxy(Signature var0)
    根据方法签名获取代理方法。其中方法签名包括方法名+方法描述(参数类型、返回类型)
  2. void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0)
    设置回调对象列表至线程上下文中
  3. void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0)
    设置回调对象列表

三、代理方法

前面已经将代理类和代理对象的内容分析的十分清楚了,代理对象会重写父类(也即,代理类)的方法,而在实际调用中会将代理类对象、被代理方法Method对象、方法参数、代理方法MethodProxy对象传给MethodInterceptor对象。这其中MethodProxy我们还是比较陌生的,本章节将会一步步拆解其内部逻辑。

3.1 方法代理属性

public class MethodProxy {
    private Signature sig1;
    private Signature sig2;
    private CreateInfo createInfo;
    
    private final Object initLock = new Object();
    private volatile FastClassInfo fastClassInfo;
}

MethodProxy共有5个属性:

  • sig1、sig2
    均表示方法签名,其中sig1为被代理方法签名,sig2为代理方法签名。方法签名由方法名、入参类型、返回类型构建为Signature 对象。
  • createInfo
    由被代理类Class对象、代理类Class对象构建的CreateInfo实例(CreateInfo为MethodProxy私有内部类)。该实例创建出来就是为了后续初始化FastClassInfo对象属性的。
  • initLock
    MethodProxy对象初始化FastClassInfo对象通过Double Check Lock实现。这里创建一个空Object对象是为了加锁。
  • fastClassInfo
    该对象会在调用MethodProxy公开方法时根据createInfo进行初始化。FastClassInfo也是MethodProxy私有内部类,该对象存储了代理类的FastClass对象、被代理类的FastClass对象以及各自当前方法的index值。
    FastClass对象时Cglib在查找类方法时的效率优化机制,相比于JDK动态代理通过反射的方式,Cglib通过FastClass和方法index能够快速找到对应的方法。在第一章节,我们可以注意到在cglib生成类包下除了增强代理类,还有另外两个类就分别是被代理类和代理类的FastClass类文件。有关于FastClass后续会有文章详细讲解。

3.2 方法代理实例化

MethodProxy的构造函数为私有,仅提供一个公开静态方法用于内部创建方法代理实例。

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
	   MethodProxy proxy = new MethodProxy();
	   proxy.sig1 = new Signature(name1, desc);
	   proxy.sig2 = new Signature(name2, desc);
	   proxy.createInfo = new CreateInfo(c1, c2);
	   return proxy;
}

MethodProxy 实例化方法中就是创建代理类及被代理类对应方法的签名和CreateInfo实例。
当前除了创建对象的初始化逻辑之外,在调用MethodProxy对象的任何公开方法时,都会调用其init()方法去初始化FastClassInfo对象,便于之后的方法索引获取。

private void init() {
	if (fastClassInfo == null){
        synchronized (initLock) {
            if (fastClassInfo == null) {
                CreateInfo ci = createInfo;

                FastClassInfo fci = new FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(sig1);
                fci.i2 = fci.f2.getIndex(sig2);
                fastClassInfo = fci;
                createInfo = null;
            }
        }
    }
}

可以看到这里通过双重检查锁的方式来创建FastClassInfo对象。FastClassInfo对象的创建过程会创建并存储被代理类、代理类的FastClass对象(f1、f2),也会存储当前代理方法在被代理类、代理类FastClass对象的索引(i1、i2)便于后续快速查找方法。
FastClass对象的创建逻辑在helper方法中,此处暂时先不展开介绍,在后续文章介绍FastClass自然就理解了。值得提醒的是,代理类、非代理类的FastClass类只有在此处才会被创建,意即只有在调用MethodProxy对象公开方法才会创建对应的FastClass类。这就回答了一个问题,为什么FastClassInfo对象不是在create方法中直接创建好,而是延迟使用DCL创建呢?因为并非是所有代理类都会被调用,如果大量的FastClass创建会减慢编译速度,增加java程序的编译时间。

看过我之前单例模式文章,在讲解DCL机制的时候提到过,以上这种使用方式可能存在问题-在高并发下,有可能会使用不成熟的FastClassInfo对象,产生空指针。
解决办法:在第一重判断条件上,增加与逻辑(或直接替换):createInfo != null;

3.2 方法代理公开方法

MethodProxy共提供了5个公开方法(其中1个是静态方法):

  1. MethodProxy find(Class type, Signature sig)
    静态方法,用于获取代理类匹配的某个签名方法。其中,type参数必须是代理类Class对象。
    内部逻辑上实际调用的是代理类内部公开的CGLIB$findMethodProxy方法,可以和之前回顾下。
  2. Signature getSignature()
    返回代理方法的签名(方法名+入参类型+返回值类型)。
  3. String getSuperName()
    返回实际被代理方法的方法名。
  4. int getSuperIndex()
    返回实际被代理方法在其FastClass对象的index值
  5. Object invoke(Object obj, Object[] args)
    通过被代理类的FastClass对象调用被代理对象obj的被代理方法。注意,这里的obj不能为代理对象,代理对象调用被代理方法(代理对象重写)就又会被拦截,就会导致死循环直到栈溢出结束。
  6. Object invokeSuper(Object obj, Object[] args)
    通过代理类的FastClass对象调用代理对象的代理方法。注意,这里的obj不能为被代理对象,被代理对象没有代理方法,也会出现强转代理类型失败。

invoke是调用被代理对象的被代理方法,而invokeSuper则是调用增强代理对象的代理方法。
疑问:invoke似乎用处很小,一般都是使用invokeSuper。
下面以invokeSuper为例,梳理下具体调用过程:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init();			// 例行初始化FastClassInfo(创建被代理类、代理类FastClass)
        FastClassInfo fci = fastClassInfo;
        /**
        * 调用代理类的FastClass对象的invoke方法
        * 第一个参数为代理方法的索引ID,第二个参数为代理对象,第三个参数方法参数
        * 具体执行:就是根据索引ID找到方法,然后根据obj、args调用方法
        **/
        return fci.f2.invoke(fci.i2, obj, args);	
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

在第二个章节开头,其中Student$$EnhancerByCGLIB$2838b21a$FastClassByCGLIB$594c36f就是为代理类生成的FastClass子类文件。FastClass就是为了加速检索类方法,这里不再赘述。其invoke方法逻辑为:

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

        try {
            switch(var10001) {
            case 0:
                return new Boolean(var10000.equals(var3[0]));
            case 1:
                return var10000.toString();
            case 2:
                return new Integer(var10000.hashCode());
            case 3:
                return var10000.clone();
           ...
            case 17:
                return var10000.CGLIB$clone$4();
            case 18:
                var10000.CGLIB$outStudentName$0();
                return null;
            case 19:
                return 86327ae0.CGLIB$findMethodProxy((Signature)var3[0]);
            case 20:
                86327ae0.CGLIB$STATICHOOK1();
                return null;
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

当被代理方法很多时,这个方法代码会很长,但是理解起来其实十分简单,就是根据第一个参数index值,来执行不同的方法。代理类所有的方法都对应了一个索引,而在MethodProxy#invokeSuper方法的索引值一定是代理方法(以CGLIB开头)对应的index。代理方法在代理类中的权限为默认权限,且其内部逻辑仅是调用父类的对应方法,没有调用回调对象的拦截方法。下面给出equals()方法的代理方法示例:

final boolean CGLIB$equals$1(Object var1) {
   return super.equals(var1);
}

四、总结

回到最初的问题,当我们调用代理对象的对应方法时如何实现代理的?
我们先回顾下JDK动态代理。JDK动态代理也是会生成并实例化一个代理类,该代理类继承的父类为Proxy类,且实现了接口下的所有方法。同样的,因为是运行时生成的代理类,代理对象一定是通过接口引用的多态对象存在。当代理对象调用任何接口方法时,其内部逻辑都会调用InvocationHandler对象(仅一个)的invoke方法进行调用。因此,使用者可以自定义InvocationHandler对象,在invoke方法中实现对指定对象的代理。
但CGLIB却有所不同。CGLIB也会生成并实例化一个代理类,该代理类直接继承被代理类,且会重写所有被代理类的方法。代理类的的重写方法内部逻辑是如果有回调对象,就将方法的执行逻辑托给回调对象,该回调对象就是使用者指定的MethodInterceptor对象,使用者可以在intercept方法中实现方法逻辑的增减修改。
两者有所不同,实际上JDK动态代理类完全就是一个新类,从代理关系上来看,代理对象代理了InvocationHandler对象,InvocationHandler对象代理了需要被代理的对象(当然,这一步依赖于用户的具体实现逻辑)。而CGLIB增强类实际上是被代理类的子类,利用重写方法的机制实现方法逻辑的增强,并提供回调对象供使用者自定义增强逻辑。因此,CGLIB生成的类应该算是动态类型增强,好过理解为代理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值