Introduksjon til Javassist

1. Oversikt

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

Enkelt sagt gjør dette biblioteket prosessen med å manipulere Java bytecode enklere ved å bruke et API på høyt nivå enn den i JDK.

2. Maven avhengighet

For å legge til Javassist-biblioteket i prosjektet vårt må vi legge til javassist inn i vår pom:

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

3. Hva er Bytecode?

På et veldig høyt nivå, hver Java-klasse som er skrevet i vanlig tekstformat og kompilert til bytekode - et instruksjonssett som kan behandles av Java Virtual Machine. JVM oversetter bytekodeinstruksjoner til monteringsanvisninger på maskinnivå.

La oss si at vi har en Punkt klasse:

offentlig klasse Punkt {privat int x; privat int y; offentlig ugyldig bevegelse (int x, int y) {this.x = x; this.y = y; } // standardkonstruktører / getters / setters}

Etter kompilering, Punkt.klasse filen som inneholder bytekoden, blir opprettet. Vi kan se bytekoden til den klassen ved å utføre javap kommando:

javap -c Point.class

Dette vil skrive ut følgende utdata:

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 // Field y: I 14: return public void move (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: retur}

Alle disse instruksjonene er spesifisert av Java-språket; et stort antall av dem er tilgjengelige.

La oss analysere bytekodeinstruksjonene til bevege seg() metode:

  • aload_0 instruksjonen laster en referanse på stakken fra den lokale variabelen 0
  • iload_1 laster en int-verdi fra den lokale variabelen 1
  • putfield setter et felt x av vårt objekt. Alle operasjoner er analoge for felt y
  • Den siste instruksjonen er en komme tilbake

Hver linje med Java-kode er samlet til bytekode med riktige instruksjoner. Javassistbiblioteket gjør det enkelt å manipulere den bytekoden.

4. Generere en Java-klasse

Javassist-biblioteket kan brukes til å generere nye Java-klassefiler.

La oss si at vi vil generere en JavassistGeneratedClass klasse som implementerer a java.lang.Cloneable grensesnitt. Vi vil at klassen skal ha en id innen int type. De ClassFile brukes til å lage en ny klassefil og FieldInfo brukes til å legge til et nytt felt i 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); jfr .addField (f); 

Etter at vi oppretter en JavassistGeneratedClass.class vi kan hevde at den faktisk har en id felt:

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

5. Laste Bytecode-instruksjoner i klasse

Hvis vi vil laste inn bytekodeinstruksjoner for en allerede eksisterende klassemetode, kan vi få en CodeAttribute av en spesifikk metode i klassen. Da kan vi få en CodeIterator for å gjenta alle instruksjoner for bytekode for den metoden.

La oss laste alle bytekodeanvisningene til bevege seg() metoden for Punkt klasse:

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

Vi kan se alle bytekodeanvisningene til bevege seg() metode ved å samle bytekoder til listen over operasjoner, som vist i påstanden ovenfor.

6. Legge til felt i eksisterende klasse Bytecode

La oss si at vi vil legge til et felt av int skriv inn bytekoden til den eksisterende klassen. Vi kan laste den klassen ved hjelp av ClassPoll og legg til 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); jfr .addField (f); 

Vi kan bruke refleksjon for å verifisere det id feltet finnes på Punkt klasse:

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

7. Legge til konstruktør i klasse Bytecode

Vi kan legge til en konstruktør i den eksisterende klassen som er nevnt i et av de foregående eksemplene ved å bruke en addInvokespecial () metode.

Og vi kan legge til en parameterløs konstruktør ved å påkalle 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 ()); cf.addMethod (minfo);

Vi kan se etter tilstedeværelsen til den nyopprettede konstruktøren ved å itere over bytecode:

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

8. Konklusjon

I denne artikkelen introduserte vi Javassist-biblioteket, med målet å gjøre manipulering av kodekode enklere.

Vi fokuserte på kjernefunksjonene og genererte en klassefil fra Java-kode; Vi har også gjort noen bytecode-manipulasjoner av en allerede opprettet Java-klasse.

Implementeringen av alle disse eksemplene og kodebitene finnes i GitHub-prosjektet - dette er et Maven-prosjekt, så det skal være enkelt å importere og kjøre som det er.


$config[zx-auto] not found$config[zx-overlay] not found