2

我正在尝试拦截带有注释的构造函数@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");
    }
}
4

1 回答 1

1

这种情况下的问题是构造函数注入。为了对构造函数进行变基,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.
    }
}

不幸的是,附加类型对于为重新定位的构造函数创建唯一签名是必需的。使用方法,Byte Buddy 可以更改名称,但对于构​​造函数,这是不可能的,因为它们必须在类文件中命名<init>才能被识别为构造函数。

Byte Buddy 尝试仅在检测类型加载辅助类。根据虚拟机,加载引用另一个类的类会导致加载引用的类型。如果此类型是检测类,则检测会中止正在进行的循环检测。

因此,Byte Buddy 确保仅在可以确定已加载检测类型之后的第一个可能点加载任何辅助类型。它通过在检测类的类初始化程序中添加自初始化来实现这一点。在某种程度上,Byte Buddy 添加了一个块:

static {
  ByteBuddy.loadAuxiliaryTypes(MyClass.class);
}

如果在反射类之前没有执行此块,则不会加载辅助类型并抛出您遇到的异常。如果您致电:

Class.forName("test.MyClass", true, getClass().getClassLoader());

而不是loadClass,如果第二个参数指示急切地执行类初始化程序,则不会出现问题。此外,如果您创建实例,则会执行初始化程序。

当然,这并不令人满意,我现在添加一些逻辑来决定辅助类型是否可以在检测期间加载以避免此类错误。

于 2015-12-12T15:26:58.910 回答