• 动态代理系列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)

  • 相关阅读:
    c++ stl中的二分查找
    2015年---移动端webapp知识总结
    移动端网站优化指南-WAP篇
    ASO优化总结(基于网络分享的知识总结归纳)
    验证数字的正则表达式集
    个人的浏览器重置样式表(总结)
    微信或移动端网页的meta
    移动端字体和字体大小规范
    min-device-pixel-ratio
    Emmet语法实例(帮助快速开发)
  • 原文地址:https://www.cnblogs.com/lyhero11/p/15557389.html
Copyright © 2020-2023  润新知