CGLIB动态代理对象执行流程分析

520 阅读4分钟

前言

都说CGLIB动态代理对象执行方法的速度相较于JDK动态代理更快,那么为什么更快,实际是因为CGLIB中采用了FastClass机制,本篇文章将对CGLIB动态代理对象执行某一个方法的流程进行分析,并引出对FastClass机制的讲解。

正文

一. 示例工程搭建

首先编写两个被代理类。

public class KafkaService {

    public boolean produce() {
        System.out.println("Kafka message produced.");
        return true;
    }

}

public class RedisService {

    public void assembleName(String name) {
        System.out.println("Name assembled: " + name);
    }

    public void assembleAge(int age) {
        System.out.println("Age assembled: " + age);
    }

}

然后编写一个ConnectHelper实现MethodInterceptor接口,并提供一个公共方法来为被代理对象创建代理对象。

public class ConnectHelper implements MethodInterceptor {

    public <T> T getInstance(Class<T> aClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(aClass);
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    /**
     * @param o 代理对象
     * @param method 被代理对象的方法
     * @param objects 被代理对象的方法参数类型
     * @param methodProxy 被代理对象的方法的代理
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
            throws Throwable {
        prepareConnect();
        // 代理对象继承了被代理对象,所以这里需要执行父类方法
        Object invokeResult = methodProxy.invokeSuper(o, objects);
        closeRelease();
        return invokeResult;
    }

    private void prepareConnect() {
        System.out.println("连接建立。");
    }

    private void closeRelease() {
        System.out.println("连接释放。");
    }

}

编写测试程序,如下所示。

public class Client {

    public static void main(String[] args) {
        ConnectHelper connectHelper = new ConnectHelper();

        KafkaService kafkaService = connectHelper.getInstance(
                KafkaService.class);
        kafkaService.produce();

        RedisService redisService = connectHelper.getInstance(
                RedisService.class);
        redisService.assembleName("DogLee");
        redisService.assembleAge(25);
    }

}

执行结果如下所示。

aJNTE3n_fmb5VdJrB_k_gIDlmAiLSKly8Q2MzgZIVPg

二. CGLIB动态代理对象方法执行流程分析

通过在程序中添加如下代码,可以将CGLIB动态代理类保存下来,如下所示。

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 目录);

第二节中的KafkaServiceCGLIB动态代理类会生成如下三个。

6CUYcsWxeT67p_9t5m4nf234paFu7Gj12aPxBZC8CI8

实际上CGLIB一开始只会生成KafkaService$$EnhancerByCGLIB$$b70fe9a2动态代理类,另外两个带FastClass标志的类是第一次执行代理方法时生成的,这点本节后面再详细分析,现在先看一下CGLIB动态代理类的内容,如下所示。

public class KafkaService$$EnhancerByCGLIB$$b70fe9a2 extends KafkaService implements Factory {
    
    // ...
	
    // 被代理类的方法的方法对象
    private static final Method CGLIB$clone$4$Method;
    // 被代理类的方法的代理对象
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        
	// ...
		
        CGLIB$produce$0$Method = ReflectUtils.findMethods(new String[]{"produce", "()Z"}, (var1 = Class.forName("cn.sakura.sacrifice.dynamic.connecttest.cglibproxy.KafkaService")).getDeclaredMethods())[0];
        CGLIB$produce$0$Proxy = MethodProxy.create(var1, var0, "()Z", "produce", "CGLIB$produce$0");
    }

    final boolean CGLIB$produce$0() {
	// 最终,在这里调用被代理对象的方法
        return super.produce();
    }

    public final boolean produce() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
	    // 首先,在这里调用MethodInterceptor的intercept()方法
            Object var1 = var10000.intercept(this, CGLIB$produce$0$Method, CGLIB$emptyArgs, CGLIB$produce$0$Proxy);
            return var1 == null ? false : (Boolean)var1;
        } else {
            return super.produce();
        }
    }

    // ...
	
}

那么可以对CGLIB动态代理类的特点进行如下归纳。

  • CGLIB动态代理类继承了被代理类,是被代理类的子类;
  • CGLIB动态代理类实现了Factory接口;
  • CGLIB动态代理类中持有被代理类方法的方法对象(Method)和方法的代理对象(MethodProxy);
  • 调用CGLIB动态代理类的代理方法,会调用到MethodInterceptorintercept() 方法。

所以CGLIB动态代理生成的代理类是被代理类的子类,以及当代理类调用方法时,会通过MethodInterceptor来调用被代理类的方法和增强方法。那么现在从代理类的produce() 方法开始,探究一下CGLIB的代理方法的执行步骤和原理。

已知代理类的produce() 方法会调用到MethodInterceptorintercept() 方法,在例子中MethodInterceptorConnectHelper,所以看一下ConnectHelperintercept() 方法,如下所示。

/**
 * @param o 代理对象
 * @param method 被代理对象的方法
 * @param objects 被代理对象的方法参数类型
 * @param methodProxy 被代理对象的方法的代理
 */
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
        throws Throwable {
    prepareConnect();
    // 代理对象继承了被代理对象,所以这里需要执行父类方法
    Object invokeResult = methodProxy.invokeSuper(o, objects);
    closeRelease();
    return invokeResult;
}

因为intercept() 方法的第一个参数为代理对象本身,而代理对象是被代理对象的子类,所以要执行被代理对象的方法,需要调用MethodProxyinvokeSuper() 方法,探究invokeSuper() 方法前,先看一下MethodProxy是如何被生成的。在CGLIB代理类的静态代码块中,有如下两行代码。

// 获取被代理类的方法对象
CGLIB$produce$0$Method = ReflectUtils.findMethods(new String[]{"produce", "()Z"}, (var1 = Class.forName("cn.sakura.sacrifice.dynamic.connecttest.cglibproxy.KafkaService")).getDeclaredMethods())[0];
// 创建被代理类的方法的代理对象
CGLIB$produce$0$Proxy = MethodProxy.create(var1, var0, "()Z", "produce", "CGLIB$produce$0");

MethodProxy是通过MethodProxycreate() 方法创建的,下面看一下create() 方法的实现。

public class MethodProxy {

    // 被代理对象的方法的签名,这里为produce
    private Signature sig1;
    // 代理对象中的方法的签名,这里叫CGLIB$produce$0,代理对象中有一个方法就叫做CGLIB$produce$0()
    // 通过CGLIB$produce$0()方法可以调用到代理对象的父对象(即被代理对象)的produce()方法
    private Signature sig2;
    // CreateInfo中保存着代理类KafkaService$$EnhancerByCGLIB$$b70fe9a2和被代理类KafkaService的Class对象
    private MethodProxy.CreateInfo createInfo;

    // ...

    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 MethodProxy.CreateInfo(c1, c2);
        return proxy;
    }

    // ...

}

MethodProxycreate() 方法就是创建一个MethodProxy对象出来并初始化其中的一些字段,sig1sig2字段记录方法信息,createInfo字段记录代理类和被代理类的Class对象信息,这些都是为后面引入FastClass机制做准备。

现在回到ConnectHelperintercept() 方法中,要调用被代理对象的方法,需要通过MethodProxyinvokeSuper() 方法,那么下面进入invokeSuper() 方法,分析一下怎么最终调用到被代理对象的方法。invokeSuper() 方法实现如下。

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        // 生成FastClass
        this.init();
        // 获取FastClassInfo
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        // 通过代理类对应的FastClass来执行方法
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}

private void init() {
    if (this.fastClassInfo == null) {
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                // CreateInfo中有方法签名以及代理类和被代理类的Class对象
                MethodProxy.CreateInfo ci = this.createInfo;
                // 创建FastClassInfo,然后开始初始化字段
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                // 创建被代理类对应的FastClasss并赋值给CreateInfo的f1字段
                fci.f1 = helper(ci, ci.c1);
                // 创建代理类对应的FastClass并赋值给CreateInfo的f2字段
                fci.f2 = helper(ci, ci.c2);
                // 计算produce在f1中的索引
                fci.i1 = fci.f1.getIndex(this.sig1);
                // 计算CGLIB$produce$0在f2中的索引
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}

invokeSuper() 方法中会首先调用init() 方法,在init() 方法中会为代理类和被代理类生成FastClass,代理类对应的FastClass会为代理类的每个方法生成一个索引,同理被代理类对应的FastClass也会为被代理类的每个方法生成一个索引,然后会根据当前需要调用的方法的信息(代理类中需要被调用的方法名为CGLIB$produce$0,被代理类中需要被调用的方法名为produce),计算各自的方法在各自的FastClass中的索引,并赋值给CreateInfof1f2字段。下图是init() 方法执行后,MethodProxy中的CreateInfo的值的情况。

k8U1l0epv3rF9zAdYz4CCAOa9_TBnoq7pqVt6nFjhb0

上图所示的CreateInfo表明被代理类KafkaServiceproduce() 方法在其对应的FastClass中的索引为0,代理类KafkaService$$EnhancerByCGLIB$$b70fe9a2CGLIB$produce$0()方法在其对应的FastClass中的索引为18。那么这个索引有什么用呢,继续看invokeSuper() 的如第三行代码。

// f2是代理对象对应的FastClass
// fci.i2是代理对象方法CGLIB$produce$0()在FastClass中的索引
// obj是代理对象本身
return fci.f2.invoke(fci.i2, obj, args);

所以在invokeSuper() 方法中就调用到了代理对象对应的FastClassinvoke() 方法,如下所示。

public Object invoke(int paramInt, Object paramObject, Object[] paramArrayOfObject) throws InvocationTargetException {
    try {
        switch (paramInt) {
            case 0:
            // ...
            case 18:
                return new Boolean(((KafkaService$$EnhancerByCGLIB$$b70fe9a2)paramObject).CGLIB$produce$0());
            // ...
            case 20:
                KafkaService$$EnhancerByCGLIB$$b70fe9a2.CGLIB$STATICHOOK1();
                return null;
        }
    } catch (Throwable throwable) {
        throw new InvocationTargetException(null);
    }
    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

所以到这里就可以看明白了,通过传入的代理对象paramObject和代理对象的方法索引paramInt,可以快速的调用到代理对象的CGLIB$produce$0()方法,而代理对象的CGLIB$produce$0()方法是下面这样。

final boolean CGLIB$produce$0() {
    return super.produce();
}

所以最终就调用到了被代理对象的produce() 方法,而且这样的好处就在于通过方法索引的方式来调用能够比反射调用更快,这也就是CGLIB中采用FastClass机制来执行比JDK动态代理执行更快的原因。

总结

CGLIB中代理对象执行方法的流程可以总结如下。

  1. 调用代理对象重写的produce() 方法;
  2. 在代理对象的produce() 方法中调用到拦截器MethodInterceptorintercept() 方法;
  3. MethodInterceptorintercept() 方法中调用到MethodProxyinvokeSuper() 方法;
  4. MethodProxyinvokeSuper() 方法中调用到代理对象对应的FastClassinvoke() 方法;
  5. 在代理对象对应的FastClassinvoke() 方法中调用到代理对象为produce() 方法生成的CGLIB$produce$0()方法;
  6. 在代理对象的CGLIB$produce$0()方法中调用到被代理对象的produce() 方法。

那么CGLIBJDK动态代理的区别可以概括如下。

  1. JDK动态代理类实现了被代理类的接口,CGLIB动态代理类继承了被代理类;
  2. JDK动态代理类和CGLIB动态代理都是在运行期生成字节码,并且JDK动态代理字节码生成快于CGLIB
  3. JDK动态代理执行方法时是基于反射调用,而CGLIB动态代理执行方法时是基于FastClass机制,所以CGLIB执行效果更高。

还有几个常见问题。

  1. 接口是否可以被代理。可以,比如MyBatis的映射接口。
  2. CGLIB的动态代理类,能不能再被CGLIB动态代理。不能,因为CGLIB动态代理类实现了Factory接口,如果再为CGLIB动态代理类生成动态代理类,在生成动态代理类时会报错,因为第二个动态代理类会重复实现Factory接口,这是不被允许的。
  3. CGLIB的动态代理类,能不能再被JDK动态代理。不能。
  4. JDK的动态代理类,能不能再被CGLIB动态代理。不能,因为JDK动态代理类是final类。
  5. JDK的动态代理类,能不能再被JDK动态代理。如果使用同一个InvocationHandler,则不能;否则,能。