1. 程式人生 > >深入理解字節碼理解invokeSuper無限循環的原因

深入理解字節碼理解invokeSuper無限循環的原因

UC declare oca tcl ron try 快速定位 on() nal

來一段簡單的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()是增強方法,註冊了攔截調用,所以才會出現循環調用,最終導致棧深操作過大範圍,出現內存溢出。

 
 

深入理解字節碼理解invokeSuper無限循環的原因