【问题标题】:Why does cglib not proxy super invocations?为什么 cglib 不代理超级调用?
【发布时间】:2013-11-21 20:56:29
【问题描述】:

我有下面的结构(我用注解@Intercepted来表示被拦截的方法):当我调用被拦截的方法为intercepted()而不使用 super 关键字按预期调用拦截器。但是,当以以下方式调用 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
   }
}

【问题讨论】:

  • 你使用什么版本的cglib?您是否尝试过降级/升级它?
  • 我用它作为依赖来客观化。我不确定这是否与版本有关。
  • 您可能想将问题标题更改为:为什么 cglib 不代理超级调用? 或类似的东西。

标签: java interceptor cglib


【解决方案1】:

答案在于生成的字节码,包括 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。这意味着,如果您从BaseImpl$$cglib 的实例调用intercepted,则所选方法将为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 一点),因此没有代理框架可以真正做到您想要的。

【讨论】:

  • 其他拦截技术可以解决这个问题吗?
  • 您可以直接检测超类。这可以通过使用更强大的检测库(例如 javassist 或 ASM)来实现,您可以在其中重写类定义而不仅仅是创建子类。可以在应用程序启动之前通过覆盖其定义或在运行时使用 Java 代理(通过 Attach API)重新定义类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
  • 2012-05-26
  • 2013-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多