为什么cglib不代理超级调用?

时间:2013-11-21 20:56:30

标签: java interceptor cglib

我在下面有以下结构(我使用注释@Intercepted来表示截获的方法):当我在不使用时将截取的方法称为intercepted() 超级关键字拦截器按预期调用。但是,以下列方式调用super.intercepted()时,永远不会调用拦截。为什么会这样?

public class Base {
   @Intercepted
   public void intercepted() {}
}

public class BaseImpl extends Base {    
   public void doSomething() {
     super.intercepted(); //<-- does not work
     intercepted(); //<--- without the super, it works
   }
}

1 个答案:

答案 0 :(得分:3)

答案在于生成的字节代码,包括Java编译器为super.method生成的字节代码与this.method相比,以及cglib为代理生成的字节代码。为了拦截方法,cglib将新类添加到类型层次结构中。然后,代理类的所有实例都将具有不同的运行时类型,该类型是代理类的子类。对于您的示例,这会产生类似于以下类型的类型层次结构:

class Base { 
  void intercepted() { ... }
}

class BaseImpl extends Base { 
  void doSomething() { ... }
}

class BaseImpl$$cglib extends BaseImpl { 
  @Override void intercepted() { ... }
}

现在Java编译器已经到位。 Java编译器通过其中一个字节代码指令调用方法。这些指令中的每一个都会导致不同的运行时行为。这里重要的两条指令是最常见的:

  • INVOKEVIRTUAL:调用this.intercepted由Java编译器转换为动态/虚方法调用。作为动态调用的结果,Java运行时将查看当前类型的虚方法表,以决定调用哪个方法。方法是bound dynamically。这意味着,如果您从intercepted的实例调用BaseImpl$$cglib,则所选方法将为BaseImpl$$cglib.intercepted。如果您从类型BaseImpl$$cglib调用方法,则调用的方法将改为Base.intercepted,因为BaseImpl不会覆盖intercepted。显然,Base.intercepted将调用其自己的类中定义的方法。

  • INVOKESTATIC:静态调用在运行时未动态绑定。为了调用super.intercepted,Java运行时应该调用超类型的虚方法表的方法。这意味着,BaseImpl将明确引用Base的方法表。因此,永远不会查询BaseImpl$$cglib的虚方法表,也无法拦截调用。这基本上与无法拦截静态方法的原因相同。在字节代码中,静态方法和超级调用的处理方式完全相同。

这就是说,对于super.intercepted,cglib代理永远不会被命中。无法在Java中加载已加载的类(嗯,您可以push this rule一点),这样任何代理框架都无法实际执行您想要的操作。