In questo post analizzeremo la libreria Javassist (Java programming assistant) sviluppata da Shigeru Chiba professore presso il Tokyo Institute of Technology, ora un sotto progetto della Community JBoss. Questa libreria permette di manipolare il bytecode di una applicazione Java , in modo da poter cambiare l'implementazione di una classe a runtime (durante cioè il tempo di esecuzione dell'applicazione).Questa manipolazione del codice è ottenuta durante la fase di caricamento della classe oggetto di modifica (load time) grazie ad un Class Loader fornito con la libreria.Javassist può essere utile nei seguenti casi:per compilare a runtime delle porzioni di codice sorgente, ad esempio fornito come input di un programma; per l'Aspect Oriented Programming (AOP), introducendo dei nuovi metodi in una classe e per inserire advice di tipo before/after/around; per la runtime reflection, introducendo un meta-oggetto che controlli le chiamate ai metodi.
Creiamo poi le seguenti classi all’interno del package it.appuntisoftware:
Somma.java
package it.appuntisoftware; public class Somma { public int aggiungi(int i) { int result = i + 1; System.out.println("Classe: " +this.getClass()); System.out.println("Sottoclasse di: " + this.getClass().getSuperclass()); System.out.println("Risultato: " + result); return result; } }
Moltiplicazione.java
package it.appuntisoftware; public class Moltiplicazione { public int moltiplica(int i) { int result = i * 2; System.out.println("Classe: " +this.getClass()); System.out.println("Sottoclasse di: " + this.getClass().getSuperclass()); System.out.println("Risultato: " + result); return result; } }
TestJavassist.java
package it.appuntisoftware; import java.lang.reflect.Method; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewMethod; public class TestJavassist { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass sum = pool.get("it.appuntisoftware.Somma"); CtClass mol = pool.get("it.appuntisoftware.Moltiplicazione"); CtClass oper = pool.makeClass("it.appuntisoftware.Operazioni"); CtMethod sMetodo = sum.getDeclaredMethod("aggiungi"); CtMethod oMetodo = CtNewMethod.copy(sMetodo, "aggiungiUno", oper, null); oper.addMethod(oMetodo); oper.setSuperclass(mol); oper.writeFile(); // scrivo il file su File System System.out.println("Ho scritto il file Operazioni!"); Class<?> operClass =oper.toClass(); Object operObject =operClass.newInstance(); Method methods=operClass.getMethod("aggiungiUno",new Class[]{Integer.TYPE} ); methods.invoke(operObject, 2); methods=operClass.getMethod("moltiplica",new Class[]{Integer.TYPE} ); methods.invoke(operObject, 2); } }
L’output del programma sarà:
Ho scritto il file Operazioni! Classe: class it.appuntisoftware.Operazioni Sottoclasse di: class it.appuntisoftware.Moltiplicazione Risultato: 3 Classe: class it.appuntisoftware.Operazioni Sottoclasse di: class it.appuntisoftware.Moltiplicazione Risultato: 4
Nel nostro programma TestJavassist.java abbiamo utilizzato solo le classi fornite da Javassist e quelle già disponibili per la Reflection in Java. Abbiamo creato a runtime una classe Operazioni a cui abbiamo aggiunto un metodo preso dalla classe Somma (aggiungi) e ridenominato aggiungiUno, poi l’abbiamo fatta estendere dalla classe Moltiplicazione (di conseguenza ha ereditato il metodo moltiplica. Dopo fatto ciò abbiamo scritto il class file su FileSystem, ritroviamo il file nella cartella del nostro progetto Eclipse.
A questo punto abbiamo invocato i due metodi della classe Operazioni utilizzando la Reflection tramite le API del package Reflect di Java.
Utilizzando un decompilatore,ad esempio Java Decompiler che abbiamo esaminato in questo articolo, possiamo vedere il codice generato per la classe Operazioni:
package it.appuntisoftware; import java.io.PrintStream; public class Operazioni extends Moltiplicazione { public int aggiungiUno(int i) { int result = i + 1; System.out.println("Classe: " + getClass()); System.out.println("Sottoclasse di: " + getClass().getSuperclass()); System.out.println("Risultato: " + result); return result; } }
oppure possiamo utilizzare il comando javap (incluso nella distribuzione Java) che disassembla il bytecode e ci mostra la struttura della classe e le firme dei metodi e dei costruttori (solo quelli con modificatore di accesso public, protected o package), ad esempio dando il comando:
javap it.appuntisoftware.Operazioni
otteniamo questo output:
COMMENTS