Introduktion til Javassist

1. Oversigt

I denne artikel vil vi se på Javasisst (Java Programming Assistant) bibliotek.

Kort sagt, dette bibliotek gør processen med at manipulere Java bytecode enklere ved hjælp af et API på højt niveau end den i JDK.

2. Maven-afhængighed

For at tilføje Javassist-biblioteket til vores projekt skal vi tilføje javassist ind i vores pom:

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

3. Hvad er Bytecode?

På et meget højt niveau hver Java-klasse, der er skrevet i almindeligt tekstformat og kompileret til bytecode - et instruktions sæt, der kan behandles af Java Virtual Machine. JVM oversætter bytecode-instruktioner til monteringsvejledning på maskinniveau.

Lad os sige, at vi har en Punkt klasse:

offentlig klasse Punkt {privat int x; privat int y; offentlig tomrumsbevægelse (int x, int y) {this.x = x; this.y = y; } // standardkonstruktører / getters / settere}

Efter kompilering er den Punkt. Klasse fil, der indeholder bytekoden, oprettes. Vi kan se bytekoden for den klasse ved at udføre javap kommando:

javap -c Point.class

Dette udskriver følgende output:

public class com.baeldung.javasisst.Point {public com.baeldung.javasisst.Point (int, int); Kode: 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 // Felt y: I 14: returner offentligt ugyldigt træk (int, int); Kode: 0: aload_0 1: iload_1 2: putfield # 2 // Felt x: I 5: aload_0 6: iload_2 7: putfield # 3 // Felt y: I 10: return}

Alle disse instruktioner er specificeret af Java-sproget; et stort antal af dem er tilgængelige.

Lad os analysere bytecode-instruktionerne til bevæge sig() metode:

  • aload_0 instruktionen indlæser en reference på stakken fra den lokale variabel 0
  • iload_1 indlæser en int-værdi fra den lokale variabel 1
  • putfield indstiller et felt x af vores objekt. Alle operationer er analoge for felt y
  • Den sidste instruktion er en Vend tilbage

Hver linje af Java-kode er samlet til bytecode med de rette instruktioner. Javassist-biblioteket gør det let at manipulere denne bytecode.

4. Generering af en Java-klasse

Javassist-biblioteket kan bruges til at generere nye Java-klassefiler.

Lad os sige, at vi vil generere en JavassistGeneratedClass klasse, der implementerer en java.lang.Cloneable interface. Vi ønsker, at klassen skal have en id felt af int type. Det ClassFile bruges til at oprette en ny klassefil og FieldInfo bruges til at føje et nyt felt til en klasse:

ClassFile cf = new ClassFile (false, "com.baeldung.JavassistGeneratedClass", null); cf.setInterfaces (ny streng [] {"java.lang.Cloneable"}); FieldInfo f = ny FieldInfo (jf. GetConstPool (), "id", "I"); f.setAccessFlags (AccessFlag.PUBLIC); jf .addField (f); 

Når vi opretter en JavassistGeneratedClass.class vi kan hævde, at det faktisk har en id Mark:

ClassPool classPool = ClassPool.getDefault (); Felt [] felter = classPool.makeClass (cf) .toClass (). GetFields (); assertEquals (felter [0] .getName (), "id");

5. Indlæsning af Bytecode-instruktioner i klasse

Hvis vi vil indlæse instruktioner for bytecode for en allerede eksisterende klassemetode, kan vi få en CodeAttribute af en bestemt metode i klassen. Så kan vi få en CodeIterator at gentage alle instruktioner for bytecode for denne metode.

Lad os indlæse alle bytecode-instruktioner fra bevæge sig() metode til Punkt klasse:

ClassPool cp = ClassPool.getDefault (); ClassFile cf = cp.get ("com.baeldung.javasisst.Point") .getClassFile (); MethodInfo minfo = jf. GetMethod ("flyt"); CodeAttribute ca = minfo.getCodeAttribute (); CodeIterator ci = ca.iterator (); Listeoperationer = ny LinkedList (); mens (ci.hasNext ()) {int index = ci.next (); int op = ci.byteAt (indeks); operations.add (Mnemonic.OPCODE [op]); } assertEquals (operationer, Arrays.asList ("aload_0", "iload_1", "putfield", "aload_0", "iload_2", "putfield", "return"));

Vi kan se alle bytekodeanvisninger fra bevæge sig() metode ved at samle bytekoder til listen over operationer, som vist i påstanden ovenfor.

6. Tilføjelse af felter til eksisterende klasse Bytecode

Lad os sige, at vi vil tilføje et felt af int skriv til bytecode for den eksisterende klasse. Vi kan indlæse den klasse ved hjælp af ClassPoll og tilføj et felt i det:

ClassFile cf = ClassPool.getDefault () .get ("com.baeldung.javasisst.Point"). GetClassFile (); FieldInfo f = ny FieldInfo (jf. GetConstPool (), "id", "I"); f.setAccessFlags (AccessFlag.PUBLIC); jf .addField (f); 

Vi kan bruge refleksion til at verificere det id feltet findes på Punkt klasse:

ClassPool classPool = ClassPool.getDefault (); Felt [] felter = classPool.makeClass (cf) .toClass (). GetFields (); Listefelterliste = Stream.of (felter) .map (felt :: getnavn) .collect (Collectors.toList ()); assertTrue (fieldsList.contains ("id"));

7. Tilføjelse af konstruktør til klasse Bytecode

Vi kan tilføje en konstruktør til den eksisterende klasse, der er nævnt i et af de foregående eksempler, ved hjælp af en addInvokespecial () metode.

Og vi kan tilføje en parameterløs konstruktør ved at påkalde a metode fra java.lang.Objekt klasse:

ClassFile cf = ClassPool.getDefault () .get ("com.baeldung.javasisst.Point"). GetClassFile (); Bytecode-kode = ny Bytecode (jf. GetConstPool ()); code.addAload (0); code.addInvokespecial ("java / lang / Object", MethodInfo.nameInit, "() V"); code.addReturn (null); MethodInfo minfo = ny MethodInfo (jf. GetConstPool (), MethodInfo.nameInit, "() V"); minfo.setCodeAttribute (code.toCodeAttribute ()); jf .addMethod (minfo);

Vi kan kontrollere tilstedeværelsen af ​​den nyoprettede konstruktør ved at gentage over bytecode:

CodeIterator ci = code.toCodeAttribute (). Iterator (); Listeoperationer = ny LinkedList (); mens (ci.hasNext ()) {int index = ci.next (); int op = ci.byteAt (indeks); operations.add (Mnemonic.OPCODE [op]); } assertEquals (operationer, Arrays.asList ("aload_0", "invokespecial", "return"));

8. Konklusion

I denne artikel introducerede vi Javassist-biblioteket med det formål at gøre manipulation af bytecode lettere.

Vi fokuserede på kernefunktionerne og genererede en klassefil fra Java-kode; Vi lavede også nogle bytecode-manipulationer af en allerede oprettet Java-klasse.

Implementeringen af ​​alle disse eksempler og kodestykker findes i GitHub-projektet - dette er et Maven-projekt, så det skal være let at importere og køre, som det er.