バイトバディへのガイド

バイトバディのガイド

1. 概要

簡単に言うと、ByteBuddyは、実行時にJavaクラスを動的に生成するためのライブラリです。

この要点の記事では、フレームワークを使用して既存のクラスを操作し、オンデマンドで新しいクラスを作成し、メソッド呼び出しをインターセプトします。

2. 依存関係

まず、プロジェクトに依存関係を追加しましょう。 Mavenベースのプロジェクトの場合、この依存関係をpom.xmlに追加する必要があります。


    net.bytebuddy
    byte-buddy
    1.7.1

Gradleベースのプロジェクトの場合、同じアーティファクトをbuild.gradleファイルに追加する必要があります。

compile net.bytebuddy:byte-buddy:1.7.1

最新バージョンはMaven Centralにあります。

3. 実行時にJavaクラスを作成する

まず、既存のクラスをサブクラス化して動的クラスを作成します。 従来のHello Worldプロジェクトを見ていきます。

この例では、Object.classのサブクラスであるタイプ(Class)を作成し、toString()メソッドをオーバーライドします。

DynamicType.Unloaded unloadedType = new ByteBuddy()
  .subclass(Object.class)
  .method(ElementMatchers.isToString())
  .intercept(FixedValue.value("Hello World ByteBuddy!"))
  .make();

ByteBuddy.のインスタンスを作成するだけでした。次に、subclass() APIを使用してObject.classを拡張し、スーパークラス(Object.classtoString()を選択しました。 )s)ElementMatchersを使用します。

最後に、intercept()メソッドを使用して、toString()の実装を提供し、固定値を返します。

make()メソッドは、新しいクラスの生成をトリガーします。

この時点で、クラスはすでに作成されていますが、まだJVMにロードされていません。 これは、生成された型のバイナリ形式であるDynamicType.Unloadedのインスタンスによって表されます。

したがって、使用する前に、生成されたクラスをJVMにロードする必要があります。

Class dynamicType = unloadedType.load(getClass()
  .getClassLoader())
  .getLoaded();

これで、dynamicTypeをインスタンス化し、その上でtoString()メソッドを呼び出すことができます。

assertEquals(
  dynamicType.newInstance().toString(), "Hello World ByteBuddy!");

dynamicType.toString()の呼び出しは、ByteBuddy.classtoString()実装のみを呼び出すため、機能しないことに注意してください。

newInstance()は、このByteBuddyオブジェクトによって表されるタイプの新しいインスタンスを作成するJavaリフレクションメソッドです。引数なしのコンストラクターでnewキーワードを使用するのと同様の方法で。

これまでのところ、動的型のスーパークラスのメソッドをオーバーライドして、独自の固定値を返すことしかできませんでした。 次のセクションでは、カスタムロジックを使用してメソッドを定義する方法について説明します。

4. メソッドの委任とカスタムロジック

前の例では、toString()メソッドから固定値を返します。

実際には、アプリケーションにはこれよりも複雑なロジックが必要です。 カスタムロジックを動的型に促進およびプロビジョニングする効果的な方法の1つは、メソッド呼び出しの委任です。

sayHelloFoo()メソッドを持つFoo.classをサブクラス化する動的タイプを作成しましょう。

public String sayHelloFoo() {
    return "Hello in Foo!";
}

さらに、sayHelloFoo()と同じシグネチャと戻り値の型の静的sayHelloBar()を使用して別のクラスBarを作成しましょう。

public static String sayHelloBar() {
    return "Holla in Bar!";
}

それでは、ByteBuddyのDSLを使用して、sayHelloFoo()のすべての呼び出しをsayHelloBar()に委任しましょう。 これにより、純粋なJavaで記述されたカスタムロジックを、実行時に新しく作成されたクラスに提供できます。

String r = new ByteBuddy()
  .subclass(Foo.class)
  .method(named("sayHelloFoo")
    .and(isDeclaredBy(Foo.class)
    .and(returns(String.class))))
  .intercept(MethodDelegation.to(Bar.class))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance()
  .sayHelloFoo();

assertEquals(r, Bar.sayHelloBar());

sayHelloFoo()を呼び出すと、それに応じてsayHelloBar()が呼び出されます。

How does ByteBuddy know which method in Bar.class to invoke?メソッドのシグネチャ、戻り値の型、メソッド名、およびアノテーションに従って、一致するメソッドを選択します。

sayHelloFoo()メソッドとsayHelloBar()メソッドの名前は同じではありませんが、メソッドのシグネチャと戻り値の型は同じです。

署名と戻り値の型が一致する呼び出し可能なメソッドがBar.classに複数ある場合は、@BindingPriorityアノテーションを使用してあいまいさを解決できます。

@BindingPriorityは整数引数を取ります–整数値が高いほど、特定の実装を呼び出す優先度が高くなります。 したがって、以下のコードスニペットでは、sayHelloBar()sayBar()よりも優先されます。

@BindingPriority(3)
public static String sayHelloBar() {
    return "Holla in Bar!";
}

@BindingPriority(2)
public static String sayBar() {
    return "bar";
}

5. メソッドとフィールドの定義

動的型のスーパークラスで宣言されたメソッドをオーバーライドできました。 クラスに新しいメソッド(およびフィールド)を追加して、さらに進んでみましょう。

Javaリフレクションを使用して、動的に作成されたメソッドを呼び出します。

Class type = new ByteBuddy()
  .subclass(Object.class)
  .name("MyClassName")
  .defineMethod("custom", String.class, Modifier.PUBLIC)
  .intercept(MethodDelegation.to(Bar.class))
  .defineField("x", String.class, Modifier.PUBLIC)
  .make()
  .load(
    getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
  .getLoaded();

Method m = type.getDeclaredMethod("custom", null);
assertEquals(m.invoke(type.newInstance()), Bar.sayHelloBar());
assertNotNull(type.getDeclaredField("x"));

Object.classのサブクラスであるMyClassNameという名前のクラスを作成しました。 次に、Stringを返し、publicアクセス修飾子を持つメソッドcustom,を定義します。

前の例で行ったように、メソッドへの呼び出しをインターセプトし、このチュートリアルの前半で作成したBar.classに委任することで、メソッドを実装しました。

6. 既存のクラスの再定義

動的に作成されたクラスを使用してきましたが、すでにロードされたクラスも使用できます。 これは、既存のクラスを再定義(またはリベース)し、ByteBuddyAgentを使用してそれらをJVMに再ロードすることで実行できます。

まず、pom.xmlByteBuddyAgentを追加しましょう。


    net.bytebuddy
    byte-buddy-agent
    1.7.1

最新バージョンはfound hereにすることができます。

それでは、前にFoo.classで作成したsayHelloFoo()メソッドを再定義しましょう。

ByteBuddyAgent.install();
new ByteBuddy()
  .redefine(Foo.class)
  .method(named("sayHelloFoo"))
  .intercept(FixedValue.value("Hello Foo Redefined"))
  .make()
  .load(
    Foo.class.getClassLoader(),
    ClassReloadingStrategy.fromInstalledAgent());

Foo f = new Foo();

assertEquals(f.sayHelloFoo(), "Hello Foo Redefined");

7. 結論

この手の込んだガイドでは、ByteBuddyライブラリの機能と、それを使用して動的クラスを効率的に作成する方法について詳しく説明しました。

そのdocumentationは、ライブラリの内部動作およびその他の側面の詳細な説明を提供します。

そして、いつものように、このチュートリアルの完全なコードスニペットはover on Githubにあります。