深入字节码理解invokeSuper无限循环的原因
来一段简单的cglib代码
1 public class SampleClass { 2 public void test(){ 3 System.out.println("hello world"); 4 } 5 6 public static void main(String[] args) { 7 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes"); 8 Enhancer enhancer = new Enhancer(); 9 enhancer.setSuperclass(SampleClass.class); 10 enhancer.setCallback(new MethodInterceptor() { 11 @Override 12 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 13 System.out.println("before method run..."); 14 Object result = proxy.invokeSuper(obj, args); 15 result = proxy.invoke(obj, args); 16 System.out.println("after method run..."); 17 return result; 18 } 19 }); 20 SampleClass sample = (SampleClass) enhancer.create(); 21 sample.test(); 22 } 23 }
代码中使用 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes")设置环境变量,此设置可以打印生成的字节码文件。
受影响的方法为:org.springframework.cglib.core.DebuggingClassWriter#toByteArray这里使用了spring的cglib包:spring的cglib包仅仅修改了cglib的类路径,实现完全相同
运行过程中,cglib会生成3个class文件,第一个class文件的生成触发点在测试类第20行,对SampleClass进行增强,生成的关键代码如下:
1 public class SampleClass$$EnhancerByCGLIB$$8ed28f extends SampleClass implements Factory { 2 private static final Callback[] CGLIB$STATIC_CALLBACKS; 3 private MethodInterceptor CGLIB$CALLBACK_0; 4 private static final Method CGLIB$test$0$Method; 5 private static final MethodProxy CGLIB$test$0$Proxy; 6 private static final Object[] CGLIB$emptyArgs; 7 8 static void CGLIB$STATICHOOK1() { 9 CGLIB$emptyArgs = new Object[0]; 10 Class var0 = Class.forName("com.example.demo.proxy.SampleClass$$EnhancerByCGLIB$$8ed28f"); 11 Class var1; 12 CGLIB$test$0$Method = ReflectUtils.findMethods(new String[]{"test", "()V"}, (var1 = Class.forName("com.example.demo.proxy.SampleClass")).getDeclaredMethods())[0]; 13 CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0"); 14 } 15 16 final void CGLIB$test$0() { 17 super.test(); 18 } 19 20 public final void test() { 21 MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; 22 if(this.CGLIB$CALLBACK_0 == null) { 23 CGLIB$BIND_CALLBACKS(this); 24 var10000 = this.CGLIB$CALLBACK_0; 25 } 26 27 if(var10000 != null) { 28 var10000.intercept(this, CGLIB$test$0$Method, CGLIB$emptyArgs, CGLIB$test$0$Proxy); 29 } else { 30 super.test(); 31 } 32 } 33 34 private static final void CGLIB$BIND_CALLBACKS(Object var0) { 35 SampleClass$$EnhancerByCGLIB$$8ed28f var1 = (SampleClass$$EnhancerByCGLIB$$8ed28f)var0; 36 if(!var1.CGLIB$BOUND) { 37 var1.CGLIB$BOUND = true; 38 Object var10000 = CGLIB$THREAD_CALLBACKS.get(); 39 if(var10000 == null) { 40 var10000 = CGLIB$STATIC_CALLBACKS; 41 if(CGLIB$STATIC_CALLBACKS == null) { 42 return; 43 } 44 } 45 46 var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0]; 47 } 48 49 } 50 51 public void setCallback(int var1, Callback var2) { 52 switch(var1) { 53 case 0: 54 this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2; 55 default: 56 } 57 } 58 59 static { 60 CGLIB$STATICHOOK1(); 61 } 62 }
测试代码第10行:enhancer.setCallback(**)将拦截器设置到增强代码中。
执行test()方法,实际上调用的是增强代码的20行test()方法,增强的方法会调用注册的拦截器。方法参数为:
Object obj 增强的SampleClass$$EnhancerByCGLIB$$8ed28f实例
Method method 原生test方法
Object[] args 此处没有参数,为空
MethodProxy proxy 生成的methodProxy
接下来我们看下methodProxy的生成:增强类静态块中调用了CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");
1 public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { 2 MethodProxy proxy = new MethodProxy(); 3 proxy.sig1 = new Signature(name1, desc); 4 proxy.sig2 = new Signature(name2, desc); 5 proxy.createInfo = new CreateInfo(c1, c2); 6 return proxy; 7 }
只是记录了一些类信息。
测试代码执行:proxy.invokeSuper(obj, args);
1 public Object invokeSuper(Object obj, Object[] args) throws Throwable { 2 try { 3 init(); 4 FastClassInfo fci = fastClassInfo; 5 return fci.f2.invoke(fci.i2, obj, args); 6 } catch (InvocationTargetException e) { 7 throw e.getTargetException(); 8 } 9 }
展开init方法
1 private void init() 2 { 3 if (fastClassInfo == null) 4 { 5 synchronized (initLock) 6 { 7 if (fastClassInfo == null) 8 { 9 CreateInfo ci = createInfo; 10 11 FastClassInfo fci = new FastClassInfo(); 12 fci.f1 = helper(ci, ci.c1); 13 fci.f2 = helper(ci, ci.c2); 14 fci.i1 = fci.f1.getIndex(sig1); 15 fci.i2 = fci.f2.getIndex(sig2); 16 fastClassInfo = fci; 17 createInfo = null; 18 } 19 } 20 } 21 }
12 fci.f1 = helper(ci, ci.c1); 13 fci.f2 = helper(ci, ci.c2);
这2行分别生成的2个fastClass类,通过类的signature快速定位方法
12 fci.f1 = SampleClass$$FastClassByCGLIB$$4f454a14
1 public class SampleClass$$FastClassByCGLIB$$4f454a14 extends FastClass { 2 3 public int getIndex(Signature var1) { 4 String var10000 = var1.toString(); 5 switch(var10000.hashCode()) { 6 case -1422510685: 7 if(var10000.equals("test()V")) { 8 return 1; 9 } 10 break; 11 12 return -1; 13 } 14 15 public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException { 16 SampleClass var10000 = (SampleClass)var2; 17 int var10001 = var1; 18 19 try { 20 switch(var10001) { 21 case 1: 22 var10000.test(); 23 return null; 24 } 25 } catch (Throwable var4) { 26 throw new InvocationTargetException(var4); 27 } 28 29 throw new IllegalArgumentException("Cannot find matching method/constructor"); 30 } 31 }
13 fci.f2 = SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b
1 public class SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b extends FastClass { 2 3 public int getIndex(Signature var1) { 4 String var10000 = var1.toString(); 5 switch(var10000.hashCode()) { 6 case -1659809612: 7 if(var10000.equals("CGLIB$test$0()V")) { 8 return 16; 9 } 10 break; 11 case -1422510685: 12 if(var10000.equals("test()V")) { 13 return 7; 14 } 15 break; 16 return -1; 17 } 18 19 public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException { 20 8ed28f var10000 = (8ed28f)var2; 21 int var10001 = var1; 22 23 try { 24 switch(var10001) { 25 case 7: 26 var10000.test(); 27 return null; 28 case 16: 29 var10000.CGLIB$test$0(); 30 return null; 31 } catch (Throwable var4) { 32 throw new InvocationTargetException(var4); 33 } 34 35 throw new IllegalArgumentException("Cannot find matching method/constructor"); 36 } 37 }
invokeSuper调用fci.f2.invoke(fci.i2, obj, args),使用的是第三个生成类,方法签名是:CGLIB$test$0
通过方法签名的hashcode映射后得到索引为16
6 case -1659809612:
7 if(var10000.equals("CGLIB$test$0()V")) {
8 return 16;
9 }
10 break;
invoke调用的时候
28 case 16: 29 var10000.CGLIB$test$0(); 30 return null;
走的这段逻辑。对比增强类可以得知CGLIB$test$0()是对原生方法的存根,执行的是最原始的逻辑。
invoke调用
1 public Object invoke(Object obj, Object[] args) throws Throwable { 2 try { 3 init(); 4 FastClassInfo fci = fastClassInfo; 5 return fci.f1.invoke(fci.i1, obj, args); 6 } catch (InvocationTargetException e) { 7 throw e.getTargetException(); 8 } catch (IllegalArgumentException e) { 9 if (fastClassInfo.i1 < 0) 10 throw new IllegalArgumentException("Protected method: " + sig1); 11 throw e; 12 } 13 }
fci.f1.invoke(fci.i1, obj, args)使用的是第二个生成类,方法签名是:test
通过方法签名的hashcode映射后得到索引为1
6 case -1422510685:
7 if(var10000.equals("test()V")) {
8 return 1;
9 }
10 break;
invoke调用的时候
21 case 1:
22 var10000.test();
23 return null;
24 }
走的这段逻辑。对比增强类可以得知test()是增强方法,注册了拦截调用,所以才会出现循环调用,最终导致栈深操作过大范围,出现内存溢出。