我正在尝试拦截使用 @Inject
注释的构造函数。这在小型单元测试的上下文中运行良好。然而,在像 Spring 这样的 DI 容器的上下文中,它会失败并出现 ClassNotFoundException
。
我设法缩小了根本原因的范围。在检测类上调用 getDeclaredConstructors
将触发此异常。有趣的是,如果我们首先创建该类的实例,问题就会消失。
例如:
public class InterceptConstructorTest {
@Test
public void testConstructorInterception() throws ClassNotFoundException {
ByteBuddyAgent.install();
new AgentBuilder.Default().type(nameStartsWith("test")).transform(new AgentBuilder.Transformer() {
@Override
public Builder<?> transform(Builder<?> builder, TypeDescription td) {
return builder.constructor(isAnnotatedWith(Inject.class))
.intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(ConstructorInterceptor.class)));
}
}).installOnByteBuddyAgent();
// If this line is uncommented, ClassNotFoundException won't be thrown
// MyClass myClass = new MyClass("a param");
// Manually load MyClass
Class<?> myClassDefinition = getClass().getClassLoader().loadClass("test.MyClass");
// Throws NoClassDefFoundError
for(Constructor<?> constructor : myClassDefinition.getDeclaredConstructors()) {
System.out.println(constructor);
}
}
}
可以找到堆栈跟踪:http://pastebin.com/1zhx3fVX
class MyClass {
@Inject
public MyClass(String aParam) {
System.out.println("constructor called");
}
}
class ConstructorInterceptor {
public static void intercept() {
System.out.println("Intercepted");
}
}
最佳答案
这种情况下的问题是构造函数注入(inject)。为了 rebase 一个构造函数,Byte Buddy 需要创建一个额外的类型并创建一个类,如下所示:
class MyClass {
private synthetic MyClass(String aParam, $SomeType ignored) {
System.out.println("constructor called");
}
@Inject
public MyClass(String aParam) {
this(aParam, null);
// Instrumentation logic.
}
}
不幸的是,附加类型对于为 rebase 构造函数创建唯一签名是必需的。对于方法,Byte Buddy 可以更改名称,但对于构造函数来说这是不可能的,因为它们必须 被命名为 <init>
在类文件中被识别为构造函数。
Byte Buddy 尝试只加载辅助类在一个类型被检测之后。根据虚拟机的不同,加载引用另一个类的类会导致加载引用的类型。如果此类型是检测类,则检测会中止循环中正在进行的检测。
因此,Byte Buddy 确保任何辅助类型仅在可以确定加载检测类型后的第一个可能点加载。它通过将自初始化 添加到检测类的类初始化器中来实现这一点。在某种程度上,字节好友添加了一个 block :
static {
ByteBuddy.loadAuxiliaryTypes(MyClass.class);
}
如果在反射类之前没有执行此 block ,则不会加载辅助类型并抛出您遇到的异常。如果你打电话:
Class.forName("test.MyClass", true, getClass().getClassLoader());
而不是 loadClass
,如果第二个参数指示急切执行类初始化程序,则不会出现此问题。此外,如果您创建实例,则会执行初始化程序。
当然,这并不令人满意,我现在正在添加一些逻辑来决定辅助类型是否可以在检测期间加载以避免此类错误。
关于java - 拦截构造函数导致 ClassNotFoundException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34219180/