Pengenalan kepada Javassist

1. Gambaran keseluruhan

Dalam artikel ini, kita akan melihat perpustakaan Javasisst (Java Programming Assistant) .

Secara sederhana, perpustakaan ini menjadikan proses memanipulasi bytecode Java lebih mudah dengan menggunakan API tahap tinggi daripada yang ada di JDK.

2. Ketergantungan Maven

Untuk menambahkan perpustakaan Javassist ke projek kami, kami perlu menambahkan javassist ke dalam pom kami:

 org.javassist javassist ${javaassist.version}   3.21.0-GA 

3. Apa itu Bytecode?

Pada tingkat yang sangat tinggi, setiap kelas Java ditulis dalam format teks biasa dan dikompilasi ke bytecode - satu set instruksi yang dapat diproses oleh Java Virtual Machine. JVM menerjemahkan arahan bytecode menjadi arahan pemasangan tahap mesin.

Katakan bahawa kita mempunyai kelas 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 }

Selepas penyusunan, fail Point.class yang mengandungi bytecode akan dibuat. Kita dapat melihat bytecode kelas tersebut dengan melaksanakan perintah javap :

javap -c Point.class

Ini akan mencetak output berikut:

public class com.baeldung.javasisst.Point { public com.baeldung.javasisst.Point(int, int); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()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 }

Semua arahan tersebut ditentukan oleh bahasa Java; sebilangan besar daripadanya ada.

Mari analisis kaedah bytecode kaedah move () :

  • arahan aload_0 memuat rujukan ke timbunan dari pemboleh ubah tempatan 0
  • iload_1 memuatkan nilai int dari pemboleh ubah tempatan 1
  • putfield adalah menetapkan medan x objek kami. Semua operasi adalah analog bagi bidang y
  • Arahan terakhir adalah pulangan

Setiap baris kod Java dikompilasi ke kod bytek dengan arahan yang betul. Perpustakaan Javassist menjadikan manipulasi bytecode itu agak mudah.

4. Menghasilkan Kelas Java

Perpustakaan Javassist dapat digunakan untuk menghasilkan fail kelas Java baru.

Katakan bahawa kita mahu menghasilkan kelas JavassistGeneratedClass yang menerapkan antara muka java.lang.Cloneable . Kami mahu kelas itu mempunyai medan id jenis int . The ClassFile digunakan untuk membuat fail kelas baru dan FieldInfo digunakan untuk menambah satu bidang baru untuk kelas :

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); 

Selepas kita membuat kelas JavassistGeneratedClass. kita dapat menegaskan bahawa ia sebenarnya mempunyai medan id :

ClassPool classPool = ClassPool.getDefault(); Field[] fields = classPool.makeClass(cf).toClass().getFields(); assertEquals(fields[0].getName(), "id");

5. Memuat Arahan Bytecode Kelas

Sekiranya kita ingin memuat arahan bytecode dari kaedah kelas yang sudah ada, kita boleh mendapatkan CodeAttribute dari kaedah tertentu kelas. Kemudian kita boleh mendapatkan CodeIterator untuk mengulangi semua arahan bytecode kaedah tersebut.

Mari beban semua arahan bytecode daripada langkah () kaedah yang Point kelas:

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 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"));

Kita dapat melihat semua arahan bytecode kaedah move () dengan menggabungkan kod bytek ke senarai operasi, seperti yang ditunjukkan dalam penegasan di atas.

6. Menambah Medan ke Kod Bytec Kelas Sedia Ada

Katakanlah bahawa kita ingin menambahkan bidang int jenis ke bytecode kelas yang ada. Kami boleh memuatkan kelas itu menggunakan ClassPoll dan menambahkan medan ke dalamnya:

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); 

Kita boleh menggunakan refleksi untuk mengesahkan bahawa medan id ada di kelas Titik :

ClassPool classPool = ClassPool.getDefault(); Field[] fields = classPool.makeClass(cf).toClass().getFields(); List fieldsList = Stream.of(fields) .map(Field::getName) .collect(Collectors.toList()); assertTrue(fieldsList.contains("id"));

7. Menambah Konstruktor ke Kod Bytecode

Kita boleh menambahkan konstruktor ke kelas yang ada yang disebut dalam salah satu contoh sebelumnya dengan menggunakan kaedah addInvokespecial () .

Dan kita boleh menambah konstruktor tanpa parameter dengan memanggil a kaedah dari kelas java.lang.Object :

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);

Kami dapat memeriksa kehadiran konstruktor yang baru dibuat dengan melakukan lelang pada kod bytecode:

CodeIterator ci = code.toCodeAttribute().iterator(); List 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. Kesimpulannya

Dalam artikel ini, kami memperkenalkan perpustakaan Javassist, dengan tujuan untuk menjadikan manipulasi bytecode lebih mudah.

Kami memberi tumpuan kepada ciri-ciri teras dan menghasilkan fail kelas dari kod Java; kami juga membuat beberapa manipulasi bytecode dari kelas Java yang sudah dibuat.

Pelaksanaan semua contoh dan coretan kod ini terdapat dalam projek GitHub - ini adalah projek Maven, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.