对构造函数的拦截会导致ClassNotFoundException [英] Interception on constructor causes ClassNotFoundException

查看:717
本文介绍了对构造函数的拦截会导致ClassNotFoundException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图拦截用 @Inject 注释的构造函数。这在小型单元测试的情况下工作正常。然而,在像Eclipse这样的DI容器的上下文中,它失败了 ClassNotFoundException

I'm trying to intercept constructors annotated with @Inject. That worked fine in the context of a small unit test. However in the context of a DI container like Spring it fails with a ClassNotFoundException.

我设法缩小了根本原因。在已检测的类上调用 getDeclaredConstructors 将触发此异常。有趣的是,如果我们首先创建该类的实例,问题就会消失。

I managed to narrow down on the root cause. Calling getDeclaredConstructors on the instrumented class will trigger this exception. Interestingly enough, if we first create an instance of that class, the problem disappears.

例如:

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

The stack stack trace can be found: 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");
    }
}


推荐答案

在这种情况下的问题是构造函数注入。为了 rebase 一个构造函数,Byte Buddy需要创建一个额外的类型并创建一个类如下:

The problem in this case is the constructor injection. In order to rebase a constructor, Byte Buddy needs to create an additional type and creates a class like the following:

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> 被识别为构造函数。

The additional type is unfortunately necessary to create a unique signature for the rebased constructors. With methods, Byte Buddy can rather change the name but for constructors that is not possible as they must be named <init> in the class file to be recognized as constructors.

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

Byte Buddy tries to only load auxiliary classes after a type was instrumented. Depending on the virtual machine, loading a class that references another class causes the loading of the referenced type. If this type is the instrumented class, the instrumentation aborts the ongoing instrumentation for the circularity.

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

Therefore, Byte Buddy makes sure that any auxiliary type is only loaded at the first possible point after it can be sure that the instrumented type is loaded. And it does this by adding a self-initialization into the instrumented class's class initializer. In a way, Byte Buddy adds a block:

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

如果在反映类之前未执行此块,则辅助类型为没有加载,你遇到的异常被抛出。如果你打电话:

If this block is not executed before reflecting on the class, the auxiliary type is not loaded and the exception you encounter is thrown. If you called:

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

而不是 loadClass ,问题不会发生在第二个参数表示急切执行类初始化程序的地方。此外,如果你创建一个实例,则会执行初始化程序。

instead of loadClass, the problem would not occur where the second parameter indicates to execute the class initializer eagerly. Also, the initializer is executed if you create an instance.

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

Of course, this is not satisfactory, I am now adding some logic to decide for an auxiliary type if it can be loaded during the instrumentation to avoid such errors.

这篇关于对构造函数的拦截会导致ClassNotFoundException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆