java - 拦截构造函数导致 ClassNotFoundException

标签 java bytecode instrumentation byte-buddy

我正在尝试拦截使用 @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/

相关文章:

java - Android Studio - 从 Google Places Details API 电话号码解析 JSON,首次运行时给出 null

java - ZXing二维码扫描黑莓崩溃

java - JDK 1.6 和 Xerces?

python - 如何从 Python 中的代码对象生成模块对象

java - 为什么这个简单的 Java 字节码会导致 StackOverflow 错误?

java - 如何使用 javaagent 访问工具方法的变量?

java - 如何将 MultivaluedMap 大写?

java - 是否可以从 jar 中编织一些类,同时排除其余类?

java - java如何优化?示例代码

profiling - 使用 Valgrind 获取指令配置文件