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、2 番目のパラメーターがクラス初期化子を熱心に実行することを示している場合、問題は発生しません。また、インスタンスを作成すると、イニシャライザが実行されます。

もちろん、これは満足のいくものではありません。このようなエラーを回避するために、インストルメンテーション中に補助タイプをロードできるかどうかを判断するロジックを追加しています。

于 2015-12-12T15:26:58.910 に答える