我在下面有以下结构(我使用注释@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
}
}
答案 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一点),这样任何代理框架都无法实际执行您想要的操作。