深入字节码理解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()是增强方法,注册了拦截调用,所以才会出现循环调用,最终导致栈深操作过大范围,出现内存溢出。

 
 

 

posted @ 2018-04-13 23:23  一粒沙的世界  阅读(1919)  评论(0编辑  收藏  举报