• Cglib源码分析 invoke和invokeSuper的差别(转)


    原文 https://blog.csdn.net/makecontral/article/details/79593732

    Cglib的实例
    
    本文重在源码的分析,Cglib的使用不再复述。
    
    //被代理类
    public class InfoDemo {
        public void welcome (String person){
            System.out.println("welcome :" + person);
        }
    }
    
    public class CglibInfoProxy implements MethodInterceptor {
        private Object target;
        public Object newInstance(Object source){
            target = source;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("before method!!!");
            Object value = methodProxy.invokeSuper(o, objects);
            //Object value = methodProxy.invoke(o, objects);
            return value;
        }
        public static void main(String[] args) {
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");
            InfoDemo instance = (InfoDemo) new CglibInfoProxy().newInstance(new InfoDemo());
            instance.welcome("zhangsan");
        }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    代理类字节码分析
    
    先来看main函数中new CglibInfoProxy().newInstance(new InfoDemo()),简单来说这个方法是将infoDemo作为一个父类,通过asm字节码生成一个子类来继承infoDemo。 
    InfoDemo$$EnhancerByCGLIB$$8b8da05b.class,就是生成的子类的字节码,称这个子类为InfoDemo的代理类。截取部分字节码显示:
    
    public class InfoDemo$$EnhancerByCGLIB$$8b8da05b extends InfoDemo implements Factory {
        private MethodInterceptor CGLIB$CALLBACK_0;
        private static final Method CGLIB$welcome$0$Method;
        private static final MethodProxy CGLIB$welcome$0$Proxy;
        //..........................................省略
         static void CGLIB$STATICHOOK1() {
            CGLIB$THREAD_CALLBACKS = new ThreadLocal();
            CGLIB$emptyArgs = new Object[0];
            Class var0 = Class.forName("CglibTest.InfoDemo$$EnhancerByCGLIB$$8b8da05b");
            Class var1;
            CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("CglibTest.InfoDemo")).getDeclaredMethods())[0];
            CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
            //..........................................省略
         }
    
         final void CGLIB$welcome$0(String var1) {
            super.welcome(var1);
        }
        //先来判断这个代理类中是否设置了方法拦截。如果设置了就调用该拦截器的intercept方法。
        //在本程序中,我们是设置了拦截器的。enhancer.setCallback(this);
        public final void welcome(String var1) {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if(this.CGLIB$CALLBACK_0 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }
            if(var10000 != null) {
                var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
            } else {
                super.welcome(var1);
            }
        }
        //..........................................省略
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    在这个代理类中,这里有两点需要注意。 
    1.它会将父类中的每一个方法,生成两个与之对应。如父类中的welcome,在代理类中就会有CGLIBwelcomewelcome0,welcome的两个方法与之对应。 
    2.每一个方法都会静态块中,经过MethodProxy.create生成对应的方法代理。如 
    MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
    
    当main函数执行到instance.welcome(“zhangsan”);这个语句时,会进入到代理类中的public final void welcome(String var1) 方法。进而执行了var10000.intercept()方法。 
    根据CglibInfoProxy中的intercept,先是会输出一句“before method!!!”,然后调用methodProxy.invokeSuper(o, objects); 
    这里的methodProxy对应的是var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy)中的CGLIB$welcome$0$Proxy。
    
    那么就相当于执行CGLIB$welcome$0$Proxy.invokeSuper(o, objects),那么来看看invokeSuper在做什么。
    
    MethodProxy 源码分析
    
    public class MethodProxy {
        //下面的前三个变量在create方法中,都已经得到了初始值了。
        private Signature sig1;
        private Signature sig2;
        private MethodProxy.CreateInfo createInfo;
        //FastClassInfo是在调用methodProxy.invoke或者methodProxy.invokeSuper中,init()会触发,后面再来细看这个。
        private volatile MethodProxy.FastClassInfo fastClassInfo;
    
        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);
            } catch (InvocationTargetException var4) {
                throw var4.getTargetException();
            }
        }
        public Object invoke(Object obj, Object[] args) throws Throwable {
            try {
                this.init();
                MethodProxy.FastClassInfo fci = this.fastClassInfo;
                return fci.f1.invoke(fci.i1, obj, args);
            } catch (InvocationTargetException var4) {
                throw var4.getTargetException();
            } catch (IllegalArgumentException var5) {
                if(this.fastClassInfo.i1 < 0) {
                    throw new IllegalArgumentException("Protected method: " + this.sig1);
                } else {
                    throw var5;
                }
            }
        }
        //本质就是要生成一个fastClassInfo,fastClassInfo里面存放着两个fastclass,f1,f2。
        //还有两个方法索引的值i1,i2。
        private void init() {
            if(this.fastClassInfo == null) {
                Object var1 = this.initLock;
                synchronized(this.initLock) {
                    if(this.fastClassInfo == null) {
                        MethodProxy.CreateInfo ci = this.createInfo;
                        MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                        //难道每一个方法,我们都去生成一个fastclass吗?
                        //不是的,每一个方法的fastclass都是一样的,只不过他们的i1,i2不一样。如果缓存中就取出,没有就生成新的FastClass
                        fci.f1 = helper(ci, ci.c1);
                        fci.f2 = helper(ci, ci.c2);
                        fci.i1 = fci.f1.getIndex(this.sig1);
                        fci.i2 = fci.f2.getIndex(this.sig2);
                        this.fastClassInfo = fci;
                        this.createInfo = null;
                    }
                }
            }
        }
    //根据一个类的信息,返回的该对象的一个Fastclass
        private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
            Generator g = new Generator();
            g.setType(type);
            g.setClassLoader(ci.c2.getClassLoader());
            g.setNamingPolicy(ci.namingPolicy);
            g.setStrategy(ci.strategy);
            g.setAttemptLoad(ci.attemptLoad);
            return g.create();
        }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    init()的过程就是生成FastClassInfo。 
    对于FastClass可以看看这篇文章https://www.cnblogs.com/cruze/p/3865180.html
    
    fci.f2.invoke(fci.i2, obj, args);fci存放了两个类的fastClass。 
    其中f1是被代理的类对应的是InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class, 
    f2InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c.class 
    这也就是为什么,在classes文件中会生成三个class文件了,一个代理类,两个fastclass. 
    f2.invoke,那就说明会调用InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c这个类的方法。
    
    代理类的FastClass
    
    那再看看代理类的fastClass的字节码长啥样 
    贴出InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c的部分字节码:
    
    public class InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c extends FastClass {
    
            public InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c(Class var1) {
                super(var1);
            }
            public int getIndex(Signature var1) {
                String var10000 = var1.toString();
                switch(var10000.hashCode()) {
                case -2055565910:
                    if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
                        return 12;
                    }
                    break;
                case -1725733088:
                    if(var10000.equals("getClass()Ljava/lang/Class;")) {
                        return 24;
                    }
                case 1013143764:
                    if(var10000.equals("CGLIB$welcome$0(Ljava/lang/String;)V")) {
                        return 17;
                    }
                }
                //----省略
            }
    
            public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
                8b8da05b var10000 = (8b8da05b)var2;
                int var10001 = var1;
    
                try {
                    switch(var10001) {
                    case 0:
                        return var10000.toString();
                    case 1:
                        return new Integer(var10000.hashCode());
    
                    case 17:
                        var10000.CGLIB$welcome$0((String)var3[0]);
                    }
                    //----省略
                }
            }
        }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    可以看到,会执行到voke方法中的 
    var10000.CGLIB$welcome$0((String)var3[0]);,而CGLIB$welcome$0其实就是直接调用了super.welcome(var1)的方法。输出结束之后就会运行完毕。
    
    那么,如果现在调用的是methodProxy.invoke(o, objects);而不是invokeSuper会是怎么样的情况呢? 
    通过上面MethodProxy的源码,可以看到当执行invoke的时候会执行到fci.f1.invoke(fci.i1, obj, args); 
    即会调用被代理类的fastclass,InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class中的invoke。 
    打开该class文件我们会发现,执行的的是welcome()。
    
    public Object invoke(Object obj, Object[] args) throws Throwable {
            try {
                    switch(var10001) {
                    case 0:
                        var10000.welcome((String)var3[0]);
                        return null;
                    case 1:
                    }
            }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    那不就是和在main函数中instance.welcome(“zhangsan”)一样的步骤了嘛,就会又开始循环调用,一直到栈溢出报错。所以,invoke会造成OOM的问题。
    

      

  • 相关阅读:
    HDU.5765.Bonds(DP 高维前缀和)
    SPOJ.TLE
    LOJ.2585.[APIO2018]新家(二分 线段树 堆)
    BZOJ.2679.Balanced Cow Subsets(meet in the middle)
    BZOJ.3293.[CQOI2011]分金币(思路)
    BZOJ.4558.[JLOI2016]方(计数 容斥)
    BZOJ.3631.[JLOI2014]松鼠的新家(树上差分)
    BZOJ.1568.[JSOI2008]Blue Mary开公司(李超线段树)
    BZOJ.1071.[SCOI2007]组队(思路)
    BZOJ.4910.[SDOI2017]苹果树(树形依赖背包 DP 单调队列)
  • 原文地址:https://www.cnblogs.com/devilwind/p/9038754.html
Copyright © 2020-2023  润新知