Inleiding tot Javassist

1. Overzicht

In dit artikel zullen we kijken naar de Javasisst (Java-programmeerassistent) bibliotheek.

Simpel gezegd, deze bibliotheek maakt het manipuleren van Java-bytecode eenvoudiger door een API op hoog niveau te gebruiken dan die in de JDK.

2. Maven Afhankelijkheid

Om de Javassist-bibliotheek aan ons project toe te voegen, moeten we javassist in onze pom:

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

3. Wat is de bytecode?

Op een zeer hoog niveau, elke Java-klasse die is geschreven in een platte tekstindeling en gecompileerd naar bytecode - een instructieset die kan worden verwerkt door de Java Virtual Machine. De JVM vertaalt bytecode-instructies in montage-instructies op machineniveau.

Laten we zeggen dat we een Punt klasse:

openbare klasse Point {private int x; privé int y; openbare ongeldige zet (int x, int y) {this.x = x; this.y = y; } // standard constructors / getters / setters}

Na compilatie, het Punt. Klasse bestand met de bytecode zal worden aangemaakt. We kunnen de bytecode van die klasse zien door de javap opdracht:

javap -c Point.class

Hiermee wordt de volgende uitvoer afgedrukt:

openbare klasse com.baeldung.javasisst.Point {openbare com.baeldung.javasisst.Point (int, int); Code: 0: aload_0 1: invokespecial # 1 // Methode java / lang / Object. "" :() V 4: aload_0 5: iload_1 6: putfield # 2 // Veld x: I 9: aload_0 10: iload_2 11: putfield # 3 // Veld y: I 14: return public void move (int, int); Code: 0: aload_0 1: iload_1 2: putfield # 2 // Veld x: I 5: aload_0 6: iload_2 7: putfield # 3 // Veld y: I 10: return}

Al deze instructies worden gespecificeerd door de Java-taal; er is een groot aantal beschikbaar.

Laten we de bytecode-instructies van het Actie() methode:

  • aload_0 instructie laadt een referentie op de stapel vanuit de lokale variabele 0
  • iload_1 laadt een int-waarde van de lokale variabele 1
  • Putfield is een veld aan het instellen X van ons object. Alle bewerkingen zijn analoog voor veld y
  • De laatste instructie is een terugkeer

Elke regel Java-code wordt met de juiste instructies naar bytecode gecompileerd. De Javassist-bibliotheek maakt het manipuleren van die bytecode relatief eenvoudig.

4. Genereren van een Java-klasse

Javassist-bibliotheek kan worden gebruikt voor het genereren van nieuwe Java-klassebestanden.

Laten we zeggen dat we een JavassistGeneratedClass klasse die een java.lang.Cloneable koppel. We willen dat die klas een ID kaart gebied van int type. De ClassFile wordt gebruikt om een ​​nieuw klassebestand te maken en Veldinfo wordt gebruikt om een ​​nieuw veld aan een klas toe te voegen:

ClassFile cf = nieuw ClassFile (false, "com.baeldung.JavassistGeneratedClass", null); cf.setInterfaces (nieuwe String [] {"java.lang.Cloneable"}); FieldInfo f = nieuwe FieldInfo (cf.getConstPool (), "id", "I"); f.setAccessFlags (AccessFlag.PUBLIC); cf.addField (f); 

Nadat we een JavassistGeneratedClass.class we kunnen stellen dat het eigenlijk een ID kaart veld:

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

5. Laden Bytecode instructies van klasse

Als we bytecode-instructies van een reeds bestaande klassemethode willen laden, kunnen we een CodeAttribute van een specifieke methode van de klas. Dan kunnen we een CodeIterator om alle bytecode-instructies van die methode te herhalen.

Laten we alle bytecode-instructies van het Actie() methode van de Punt klasse:

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 (); Lijstbewerkingen = nieuwe LinkedList (); while (ci.hasNext ()) {int index = ci.next (); int op = ci.byteAt (index); operations.add (Mnemonic.OPCODE [op]); } assertEquals (operaties, Arrays.asList ("aload_0", "iload_1", "putfield", "aload_0", "iload_2", "putfield", "return"));

We kunnen alle bytecode-instructies van het Actie() methode door bytecodes te aggregeren naar de lijst met bewerkingen, zoals weergegeven in de bovenstaande bewering.

6. Velden toevoegen aan bestaande klasse-bytecode

Laten we zeggen dat we een veld willen toevoegen van int typ naar de bytecode van de bestaande klasse. We kunnen die klasse laden met ClassPoll en voeg er een veld aan toe:

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

We kunnen reflectie gebruiken om dat te verifiëren ID kaart veld bestaat op het Punt klasse:

ClassPool classPool = ClassPool.getDefault (); Veld [] velden = classPool.makeClass (cf) .toClass (). GetFields (); Lijst veldenList = Stream.of (velden) .map (Veld :: getName) .collect (Collectors.toList ()); assertTrue (fieldsList.contains ("id"));

7. Constructor toevoegen aan klasse-bytecode

We kunnen een constructor toevoegen aan de bestaande klasse die in een van de vorige voorbeelden wordt genoemd door een addInvokespecial () methode.

En we kunnen een constructor zonder parameters toevoegen door een methode van java.lang.Object klasse:

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

We kunnen de aanwezigheid van de nieuw gemaakte constructor controleren door de bytecode te herhalen:

CodeIterator ci = code.toCodeAttribute (). Iterator (); Lijstbewerkingen = nieuwe LinkedList (); while (ci.hasNext ()) {int index = ci.next (); int op = ci.byteAt (index); operations.add (Mnemonic.OPCODE [op]); } assertEquals (operaties, Arrays.asList ("aload_0", "invokespecial", "return"));

8. Conclusie

In dit artikel hebben we de Javassist-bibliotheek geïntroduceerd, met als doel het manipuleren van bytecodes gemakkelijker te maken.

We concentreerden ons op de kernfuncties en genereerden een klassenbestand op basis van Java-code; we hebben ook wat bytecode-manipulatie gemaakt van een reeds gemaakte Java-klasse.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in het GitHub-project - dit is een Maven-project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.