如何执行代理方法
当我们拿到生成的代理类对象的时候,执行sayHello()
方法时,实际执行的是下面的代码
public final void sayHello() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy); } else { super.sayHello(); } } 复制代码
当MethodInterceptor
类型的变量(例子中就是HelloInterceptor类)不为空时,将会执行该类中的intercept()
方法
我们分析一下这个方法的参数:
- this: 也就是动态代理类的对象
- CGLIB$sayHello$0$Method:它是类型为Method的静态属性,,在
CGLIB$STATICHOOK1()
方法中赋值,而该方法在最下面的静态代码块中被调用。被赋值为Hello类里面的sayHello
方法 - CGLIB$emptyArgs:传递参数,这个表示没有参数,有参数的话也只是直接传递参数数组,很简单
- CGLIB$sayHello$0$Proxy:是cglib自己为这个方法创建的一个代理对象,内部是通过FastClass来执行原始方法的,它比通过反射来执行要快一些(以前),不过目前JDK版本的迭代,通过反射执行效率反而更加高。
栈溢出问题
当我们调用methodProxy.invoke(o, objects)
方法时,就会发生栈溢出。我们看一下这个方法的内部是如何实现的
该方法接收两个参数:
- 执行方法的对象
- 方法的参数
public Object invoke(Object obj, Object[] args) throws Throwable { try { // 会创建一个Hello类的FastClass类,该类用于较快调用指定的方法 // 主要是通过index下标来确定需要执行的方法。有兴趣可以自行反编译查看 init(); FastClassInfo fci = fastClassInfo; // 1 return fci.f1.invoke(fci.i1, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (IllegalArgumentException e) { if (fastClassInfo.i1 < 0) throw new IllegalArgumentException("Protected method: " + sig1); throw e; } } 复制代码
1中的那行代码,就是真正执行方法的语句,我们可以debug看内部的属性是什么
通过debug我们可以看到f1
是一个Hello?FastClassByCGLIB?7572df32
类的对象,我们可以在生成的动态代理类下面找到这个类,我们打开这个类,并找到它的invoke
方法
我们执行这个方法传递的参数,可以看到i1
是0,黄色框部分。
我们传递三个参数分别是:
- 0
- 动态代理类的对象
- 执行方法所需要的参数
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException { Hello var10000 = (Hello)var2; int var10001 = var1; try { switch(var10001) { case 0: var10000.sayHello(); return null; case 1: return new Boolean(var10000.equals(var3[0])); case 2: return var10000.toString(); case 3: return new Integer(var10000.hashCode()); } } catch (Throwable var4) { throw new InvocationTargetException(var4); } throw new IllegalArgumentException("Cannot find matching method/constructor"); } 复制代码
我们通过这个源码可以看出实际上执行的是var10000.sayHello()
,而var10000
是我们传递进来的动态代理对象,也就等于又执行了一次jack.sayHello();
,而jack.sayHello()
又会执行var10000.sayHello()
,所以一直互相调用,直到发生栈溢出。
不发生栈溢出的方法
我们分析完上一个会发生栈溢出的方法调用后,我们来分析一下不会发生栈溢出的方法调用的原理。
当我们调用的时methodProxy.invokeSuper(o, objects);
时,就不会发生栈溢出的问题,我们看一下这个方法的源码
public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; // 只有这一句不一样 return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } 复制代码
通过debug,我们可以看到f2
是Hello?EnhancerByCGLIB?35d006ca?FastClassByCGLIB?518e6b66_3
,它是为生成的动态代理类生成的FastClass类。
我们执行invoke方法传递的参数分别是:
- 17
- 动态代理类的对象
- 执行方法所需要的参数
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException { 35d006ca var10000 = (35d006ca)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 4: return var10000.newInstance((Callback[])var3[0]); case 5: return var10000.newInstance((Callback)var3[0]); case 6: return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]); case 7: var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]); return null; case 8: var10000.sayHello(); return null; case 9: 35d006ca.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]); return null; case 10: 35d006ca.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]); return null; case 11: var10000.setCallbacks((Callback[])var3[0]); return null; case 12: return var10000.getCallbacks(); case 13: return var10000.getCallback(((Number)var3[0]).intValue()); case 14: return var10000.CGLIB$toString$2(); case 15: return new Integer(var10000.CGLIB$hashCode$3()); case 16: return var10000.CGLIB$clone$4(); case 17: var10000.CGLIB$sayHello$0(); return null; case 18: return new Boolean(var10000.CGLIB$equals$1(var3[0])); case 19: return 35d006ca.CGLIB$findMethodProxy((Signature)var3[0]); case 20: 35d006ca.CGLIB$STATICHOOK1(); return null; } } catch (Throwable var4) { throw new InvocationTargetException(var4); } throw new IllegalArgumentException("Cannot find matching method/constructor"); } 复制代码
实际上就是直接调用父类的sayHello方法,也就是最原始的那个方法。所以这里并不会出现互相调用的局面。
还剩下一个method.invoke(o, objects)
方法没有讲解。大家可以自己去debug一下看看会不会报错,原理跟methodProxy.invoke()
类似。
总结
cglib动态代理和JDK动态代理本质的区别就是:
- cglib对于类的代理是基于继承的。cglib通过继承来实现动态代理(也可以通过接口)
- JDK只能基于接口,因为它自身需要继承Proxy类,而java不支持多继承。而JDK只能通过实现接口来实现动态代理。
cglib动态代理的过程中会生成三个Class文件(实际是五个,另外两个不需要理解),分别是:
- 原始类的动态代理类
- 原始类的FastClass类
- 动态代理类的FastClass类
cglib调用原始方法是通过FastClass的下标进行调用的。而JDK动态代理是通过反射进行调用的。
尾声
至此,cglib动态代理就讲完了,当然,ASM操作字节码生成动态代理文件部分因为不熟悉所以没有去讲解。但是不影响理解cglib动态代理的原理。写了两天,一共花了12个小时,加上debug,找资料,阅读源码,终于完成了。希望大家看到错误的地方多多包涵,我会虚心请教。
这几天会总结出cglib动态代理和JDK动态代理的区别!