Programmando molto spesso si sente la necessità di poter inserire parti di codice aggiuntive in un sistema durante il suo uso (si pensi ad esempio alla possibilità di includere plugin, in una qualsivoglia applicazione, che debbano essere utilizzabili subito dopo la loro “installazione”) vediamo come, e con che limiti, questo è realizzabile con il linguaggio della Sun.

Compilazione a runtime:

Per prima cosa è utile sapere che da una qualsiasi applicazione java è possibile richiamare il compilatore (javac) con gli usuali parametri (classpath e quant’altro) con un semplice

Main.compile(new String[] {
"-d","bin/",
"src/generatedCode/"+filename+".java" });

NB: per i vari parametri (ne ho tralasciati molti, tra cui le varie opzioni di compilazione) vi lascio alla documentazione delle API.

Loading:

Avendo a disposizione il file .class da far caricare alla nostra applicazione abbiamo 2 strade percorribili, partendo dall’assunto che conosciamo il nome della classe:

  1. Se non abbiamo informazioni specifiche sulla classe ricavarle tramite i meccanismi di reflection e istanziarne i metodi;
  2. Se abbiamo strutturato i “plugin” per implementare una interfaccia nota istanziare un oggetto (come vedremo a breve) e chiamare i metodi dell’interfaccia.

Prenderò in considerazione il 2 caso perché altrimenti divagherei troppo su un argomento abbastanza complicato ed interessante quale è la Reflection.

Prendiamo in considerazione il seguente caso:

La classe A (quella che vogliamo caricare) implementa l’interfaccia B.

ClassLoader loader =  new ClassLoader();
Class<?> cls = loader.loadClass("A");
B istanza_di_A = (B) cls.newInstance();
istanza_di_A.metodoDiA(...);

Come si può notare il procedimento è molto semplice da realizzare, vediamo adesso quale è il limite principale.

Unloading\Reloading:

Purtroppo la JVM non consente l’unloading del bytecode relativo alle classi caricate.
Prendiamo in considerazione l’idea di modificare il codice della classe A una volta dopo aver istanziato un oggetto di tale tipo nel nostro programma. Vorremmo, come sembrerebbe naturale, che, una volta caricato il bytecode della classe modificata e ricompilata, i nuovi oggetti di tipo “A” vengano creati attenendosi all’ultima versione disponibile.

Purtroppo ciò non si verifica, vediamo il perché.
Il classloader, che precedentemente abbiamo utilizzato per “caricare” in memoria il bytecode della nostra classe a runtime, come abbiamo detto non da la possibilità (ne manuale ne automatica) di gestire l’unloading delle classi istanziate dall’applicazione. Al momento della creazione di un oggetto si limita a seguire il seguente algoritmo (che riporto in modo molto semplificato):

  1. Controlla in una sua struttura dati (un hashtable) se esiste un entry per la classe che gli è stato chiesto di caricare accedendo con il nome della classe (”nome_classe”->”bytecode”);
  2. Se non trova entry in corrispondenza di tale nome ne crea una e, dopo aver letto il file .class, salva il bytecode in tale struttura ritornando quindi al programma un istanza dell’oggetto.
  3. Alternativamente, se trova un entry per il nome della classe, non si preoccupa di valutare in un qualsivoglia modo se il bytecode è aggiornato o meno; istanzia un oggetto dal bytecode precedentemente salvato e lo ritorna al programma.

Come è facile immaginare le modifiche saranno quindi effettive solo al successivo riavvio del programma.
L’unica soluzione esistente è quella di implementare un ClassLoader personalizzato la cui istanza si occupi di caricare\istanziare le classi che vogliamo usare come plugin e che, una volta che queste subiscano modifiche, possiamo “buttare via” (ponendo a null tutti i relativi riferimenti) per reinstanziarene una nuova istanza che si prenda carico di gestire il nuovo codice nella sua struttura dati privata.

Macchinosa vero come soluzione? Bhe se lo fa anche Tomcat per aggiornare le odifiche delle jsp direi che è opportuno adeguarsi )