1概要

この記事では、


Javasisst

(Java Programming Assistant)

ライブラリーを見ていきます。

簡単に言うと、このライブラリはJDKのものよりも高レベルのAPIを使用することによってJavaバイトコードを操作するプロセスをより簡単にします。


2 Mavenの依存関係

Javassistライブラリをプロジェクトに追加するには、

httpsを追加する必要があります。]

私たちのポンポンに:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>${javaassist.version}</version>
</dependency>

<properties>
    <javaassist.version>3.21.0-GA</javaassist.version>
</properties>

** 3バイトコードとは

非常に高いレベルでは、プレーンテキスト形式で書かれ、バイトコードにコンパイルされたすべてのJavaクラス – Java仮想マシンによって処理できる命令セットJVMは、バイトコード命令をマシンレベルのアセンブリ命令に変換します。


Point

クラスがあるとしましょう。

public class Point {
    private int x;
    private int y;

    public void move(int x, int y) {
        this.x = x;
        this.y = y;
    }

   //standard constructors/getters/setters
}

コンパイル後、バイトコードを含む

Point.class

ファイルが作成されます。

javap

コマンドを実行すると、そのクラスのバイトコードを見ることができます。

javap -c Point.class

これにより、次のような出力が表示されます。

public class com.baeldung.javasisst.Point {
  public com.baeldung.javasisst.Point(int, int);
    Code:
       0: aload__0
       1: invokespecial #1                 //Method java/lang/Object."<init>":()V
       4: aload__0
       5: iload__1
       6: putfield      #2                 //Field x:I
       9: aload__0
      10: iload__2
      11: putfield      #3                 //Field y:I
      14: return

  public void move(int, int);
    Code:
       0: aload__0
       1: iload__1
       2: putfield      #2                 //Field x:I
       5: aload__0
       6: iload__2
       7: putfield      #3                 //Field y:I
      10: return
}

これらの命令はすべてJava言語によって指定されています。

https://en.wikipedia.org/wiki/Java


bytecode

instruction__listings【多数あります。


move()

メソッドのバイトコード命令を分析しましょう。


  • aload

    0__命令は、スタックから参照をスタックにロードしています。

ローカル変数0
**

iload

1__はローカル変数からint値をロードしています1


  • putfield

    は、オブジェクトのフィールド

    x

    を設定しています。すべての操作は

フィールド

y

と類似
** 最後の命令は

return

です

Javaコードのすべての行は適切な命令でバイトコードにコンパイルされています。 Javassistライブラリーは、そのバイトコードの操作を比較的簡単にします。


4 Javaクラスの生成

Javassistライブラリは、新しいJavaクラスファイルを生成するために使用できます。


java.lang.Cloneable

インターフェースを実装する

JavassistGeneratedClass

クラスを生成したいとしましょう。そのクラスに

int

タイプの

id

フィールドを持たせたい

__.



ClassFile

を使って新しいクラスファイルを作成し、

FieldInfo

を使って新しいフィールドをクラス

__に追加する

ClassFile cf = new ClassFile(
  false, "com.baeldung.JavassistGeneratedClass", null);
cf.setInterfaces(new String[]{"java.lang.Cloneable"});

FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I");
f.setAccessFlags(AccessFlag.PUBLIC);
cf.addField(f);


JavassistGeneratedClass.class

を作成したら、それに実際に

id

フィールドがあることを表明できます。

ClassPool classPool = ClassPool.getDefault();
Field[]fields = classPool.makeClass(cf).toClass().getFields();

assertEquals(fields[0].getName(), "id");


5クラスのバイトコード命令のロード

既存のクラスメソッドのバイトコード命令をロードしたい場合は、クラスの特定のメソッドの

CodeAttribute

を取得できます。

それから、そのメソッドのすべてのバイトコード命令を繰り返すための

CodeIterator

を取得できます。


Point

クラスの

move()

メソッドのすべてのバイトコード命令をロードしましょう。

ClassPool cp = ClassPool.getDefault();
ClassFile cf = cp.get("com.baeldung.javasisst.Point")
  .getClassFile();
MethodInfo minfo = cf.getMethod("move");
CodeAttribute ca = minfo.getCodeAttribute();
CodeIterator ci = ca.iterator();

List<String> operations = new LinkedList<>();
while (ci.hasNext()) {
    int index = ci.next();
    int op = ci.byteAt(index);
    operations.add(Mnemonic.OPCODE[op]);
}

assertEquals(operations,
  Arrays.asList(
  "aload__0",
  "iload__1",
  "putfield",
  "aload__0",
  "iload__2",
  "putfield",
  "return"));

上のアサーションに示すように、

move()

メソッドのすべてのバイトコード命令を、オペレーションのリストにバイトコードを集約することで見ることができます。

** 6. 既存のクラスにフィールドを追加する

既存のクラスのバイトコードに

int

型のフィールドを追加したいとしましょう。

ClassPoll

を使用してそのクラスをロードし、それにフィールドを追加することができます。

ClassFile cf = ClassPool.getDefault()
  .get("com.baeldung.javasisst.Point").getClassFile();

FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I");
f.setAccessFlags(AccessFlag.PUBLIC);
cf.addField(f);

リフレクションを使用して、

Point

クラスに

id

フィールドが存在することを確認できます。

ClassPool classPool = ClassPool.getDefault();
Field[]fields = classPool.makeClass(cf).toClass().getFields();
List<String> fieldsList = Stream.of(fields)
  .map(Field::getName)
  .collect(Collectors.toList());

assertTrue(fieldsList.contains("id"));


7. クラスBytecode

へのコンストラクタの追加


addInvokespecial()

メソッドを使用して、前の例のいずれかで言及した既存のクラスにコンストラクタを追加できます。

そして、

java.lang.Object

クラスから

<init>

メソッドを呼び出すことで、パラメータなしのコンストラクタを追加できます。

ClassFile cf = ClassPool.getDefault()
  .get("com.baeldung.javasisst.Point").getClassFile();
Bytecode code = new Bytecode(cf.getConstPool());
code.addAload(0);
code.addInvokespecial("java/lang/Object", MethodInfo.nameInit, "()V");
code.addReturn(null);

MethodInfo minfo = new MethodInfo(
  cf.getConstPool(), MethodInfo.nameInit, "()V");
minfo.setCodeAttribute(code.toCodeAttribute());
cf.addMethod(minfo);

バイトコードを繰り返し処理することで、新しく作成されたコンストラクタの存在を確認できます。

CodeIterator ci = code.toCodeAttribute().iterator();
List<String> operations = new LinkedList<>();
while (ci.hasNext()) {
    int index = ci.next();
    int op = ci.byteAt(index);
    operations.add(Mnemonic.OPCODE[op]);
}

assertEquals(operations,
  Arrays.asList("aload__0", "invokespecial", "return"));


8結論

この記事では、バイトコードの操作を容易にすることを目的として、Javassistライブラリーを紹介しました。

私たちはコア機能に焦点を当て、Javaコードからクラスファイルを生成しました。また、作成済みのJavaクラスのバイトコード操作も行いました。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/libraries[GitHubプロジェクト]にあります – これはMavenプロジェクトなので、インポートするのは簡単ですし、そのまま実行します。