我在下面有以下结构(我使用注释 @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
}
}
最佳答案
答案在于生成的字节码,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
不会覆盖拦截
。显然,Base.intercepted
会调用自己类中定义的方法。INVOKESTATIC
:静态调用在运行时未动态绑定(bind)。为了调用super.intercepted
,Java 运行时应该调用父类(super class)型的虚方法表 中的方法。这意味着,BaseImpl
将显式引用Base
的方法表。因此,BaseImpl$$cglib
的虚方法表永远不会被查阅,调用也无法被拦截。这与无法拦截静态方法的原因基本相同。在字节代码中,静态方法和 super 调用的处理方式完全相同。
也就是说,对于 super.intercepted
,cglib 代理永远不会被命中。在 Java 中检测加载的类是不可能的(好吧,你可以 push this rule 一点点),因此没有代理框架可以真正做你想做的事。
关于java - 为什么 cglib 不代理 super 调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20131694/