深入理解字節碼理解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 = newEnhancer(); 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()是增強方法,註冊了攔截調用,所以才會出現循環調用,最終導致棧深操作過大範圍,出現內存溢出。
深入理解字節碼理解invokeSuper無限循環的原因