Byte Buddyで動的クラス生成

2018年3月10日

動的にクラス生成を行う必要に迫られたので調べてみたのだが、結論から言えば、おそらくはByte Buddyが最も簡単である。

※※※ だが、これについては所望の結果が得られていない ※※※

やりたいこと

あるJarファイルの中身を全部調べ、特定のクラスアノテーションがついているクラスをすべて取得し、それらすべてをstatic配列として持つクラスを作成し、その.classを同じJarファイルの特定のクラスとして書き加える。

つまり、以下のようなクラスを作成する。

class Sample {
  Class<?>classes = new Class[] {
    A.class, B.class, C.class
  };
}

最初はJavassistに目が行ったのだが、正直なところ実現方法がどうしてもわからなかった。また、配列の初期化子は使えないとの情報もあり、諦めることにした。この他にASM、cglib等があるようだが、それらを触る前にByte Buddyの簡単さがわかったので試してもいない。

Byte Buddyの簡単な例

簡単な例としては以下。これは単に新たなクラスを作成し、指定文字列を返り値としてtoString()をオーバライドするだけ。たしかにきちんと動いている。

import net.bytebuddy.*;
import net.bytebuddy.dynamic.*;
import net.bytebuddy.implementation.*;
import net.bytebuddy.matcher.*;

public class Sample1 {
  public static void main(String[]args) throws Exception {    
    DynamicType.Unloaded<?> unloadedType = new ByteBuddy()
      .subclass(Object.class)
      .method(ElementMatchers.isToString())
      .intercept(FixedValue.value("Hello World ByteBuddy!"))
      .make();

    ClassLoader classLoader = Sample1.class.getClassLoader();
    Class<?> newClass = unloadedType.load(classLoader).getLoaded();

    Object o = newClass.newInstance();
    System.out.println(o);
  }
}

所望の動作を行う例

まだ所望の動作にはなっていない。クラスとしての動作は良いのだが、バイトコードを出力させると、配列初期化子が存在していないようだ。

※コードを調べてみると、どうやら変数にアクセスされたタイミングをフックして初期化子の値を返しているだけらしく。生成コードにはこの初期化子が含まれないようだ。これ以上労力を使いたくないので調査は打ち切り。

import java.io.*;
import java.lang.reflect.*;

import net.bytebuddy.*;
import net.bytebuddy.dynamic.*;
import net.bytebuddy.implementation.LoadedTypeInitializer.*;

public class Sample2 {

  public static class Foo {}
  public static class Bar {}

  public static void main(String[]args) throws Exception {

    ForStaticField sf = new ForStaticField("CLASSES", new Class[] { Foo.class, Bar.class }) {};

    DynamicType.Unloaded<?> unloadedType = new ByteBuddy()
        .subclass(Object.class)
        .name("sample.TheClasses")
        .defineField("CLASSES", Class[].class, Modifier.PUBLIC|Modifier.STATIC)      
        .initializer(sf)
        .make();

    Class<?> dynamicType = unloadedType.load(Generator.class
        .getClassLoader())
        .getLoaded();

    Object o = dynamicType.newInstance();
    Field field = dynamicType.getDeclaredField("CLASSES");
    Class<?>[]CLASSES = (Class<?>[])field.get(null);
    for (Class c: CLASSES) {
      System.out.println("" + c);
    }

    unloadedType.saveIn(new File("tmp"));
  }
}