Javassistの紹介
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プロジェクトなので、インポートするのは簡単ですし、そのまま実行します。