动态代理系列Cglib的FastClass机制(四)

书接上文,https://www.cnblogs.com/lyhero11/p/15553458.html

Cglib代理类分析

上回书遗留了一个疑问:cglib是如何动态的对委托类的方法进行调用的,我们说由于Java反射的一些性能问题,cglib使用了一种叫做FastClass的技巧来优化这个调用。
接下来分析下Cglib生成的代理类,来研究一下所谓的FastClass机制。

先在设置一下代理类的输出路径:

//设置cglib生成的代理类输出到temp文件夹
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\temp");

然后就可以去D:\temp用反编译工具看生成的代理类的源码了,推荐使用Luyten反编译,jd-gui有些代码反编译不出来。

如图可以看到对委托类LancerEvolutionVI动态了3个类:

先来看一下LancerEvolutionVI$$EnhancerByCGLIB$$9332002c,这个就是我们的对象对应的代理类:

public class LancerEvolutionVI$$EnhancerByCGLIB$$9332002c extends LancerEvolutionVI implements Factory{
    final void CGLIB$speed$0() {
        super.speed();
    }
    
    public final void speed() {
        MethodInterceptor loc_1;
        MethodInterceptor loc_0;
        //方法拦截器,对应我们的CarMethodInterceptor
        if ((loc_0 = (loc_1 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            loc_1 = (loc_0 = this.CGLIB$CALLBACK_0);
        }
        if (loc_0 != null) {
            //执行拦截方法,实现代理,对应我们实现MethodInterceptor接口的intercept方法
            loc_1.intercept((Object)this, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$speed$0$Method, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$emptyArgs, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$speed$0$Proxy);
            return;
        }
        super.speed();
    }
}

然后,Object ret = methodProxy.invokeSuper(proxyObj, args)里我们用的invokeSuper这个方法,其实理论上这里我们有了proxyObj也就是LancerEvolutionVI$$EnhancerByCGLIB代理对象,那么其实可以直接调用CGLIB$speed$0()来调用父类也就是委托类的方法了,为什么要用invokeSuper呢?接着看MethodProxy.invokeSuper方法的源代码:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init();
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args); //fci.i2是fastClass里边存的Method的索引
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}

传说中的FastClass出现了,简单理解一下FastClass:为一个对象A创建它的FastClass对象,这个FastClass对象相当于A的方法索引,根据A的方法名生成并关联一个index、每个index对应A的一个方法。后续只要根据这个index以及A的实例,就可以调用fastClass的invoke(instanceOfA, index, args)方法来快速的调用A的方法了。实现了Java反射的“运行时动态调用指定类的方法”的功能,但是使用了不同的机制。我们知道,java反射调用方法是比较“重”的操作,要经过一系列的权限验证、通过native方法请求jvm去方法区查找方法定义、以及最后的invoke仍然可能要通过JNI调用native方法。而相比之下,FastClass方式则跟一般的一个普通的对象方法调用没啥区别、只是多了一步根据index判断调用委托类的哪个方法这一步骤、性能损耗基本没有。
仿写一个例子,加深一下FastClass技巧的印象:

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 为每个委托类动态生成FastClass类
 * */
@Slf4j
public class DummyServiceFactClass {

    //sign方法签名
    public int getIndex(String sign){
        switch (sign){
            case "service1":
                return 1;
            case "service2":
                return 2;
        }
        return 0;
    }

    //index方法索引, obj调用对象, args方法参数
    public Object invoke(int index, Object obj, Object[] args){
        DummyService dummyService = (DummyService) obj;
        switch (index){
            case 1:
                return dummyService.service1((String)args[0]);
            case 2:
                dummyService.service2((String)args[0]);
                return null;
        }
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        DummyService dummyService = new DummyService();
        //使用FastClass
        DummyServiceFactClass dummyServiceFactClass = new DummyServiceFactClass();
        int index1 = dummyServiceFactClass.getIndex("service1");
        int index2 = dummyServiceFactClass.getIndex("service2");

        Object ret1 = dummyServiceFactClass.invoke(1, dummyService, new Object[]{"123"});
        Object ret2 = dummyServiceFactClass.invoke(2, dummyService, new Object[]{"456"});

        log.info("ret1 = {}, ret2 = {}", ret1, ret2);

        //使用反射
        Class clazz = dummyService.getClass();
        Method method1 = clazz.getDeclaredMethod("service1", String.class);
        Method method2 = clazz.getDeclaredMethod("service2", String.class);

        ret1 = method1.invoke(dummyService,"123f");
        ret2 = method2.invoke(dummyService, "345f");

        log.info("ret1 = {}, ret2 = {}", ret1, ret2);
    }
}

输出

10:40:32.174 [main] INFO com.wangan.springbootone.aop.DummyService - service1 , param = 123
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service2, param = 456
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyServiceFactClass - ret1 = this is service1, param = 123, ret2 = null
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service1 , param = 123f
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service2, param = 345f
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyServiceFactClass - ret1 = this is service1, param = 123f, ret2 = null

总结

FastClass机制算是一种技巧层面的东西,在java内存里边维护一个index值和对象的方法之间的逻辑映射,然后运行期可以根据index和实例来动态调用方法、且不用使用比较“重”的Java反射功能。

参考:

漫画:AOP 面试造火箭事件始末 (qq.com)

cglib源码分析(四):cglib 动态代理原理分析 - cruze_lee - 博客园 (cnblogs.com)

Spring AOP 是怎么运行的?彻底搞定这道面试必考题 - 云+社区 - 腾讯云 (tencent.com)

posted on 2021-11-15 17:29  肥兔子爱豆畜子  阅读(3606)  评论(4编辑  收藏  举报

导航