JAVA,  Programmazione

Caricare jars e classi Java dinamicamente

Java fornisce una funzionalità in cui è possibile modificare le variabili di ambiente di sistema utilizzando l’oggetto System.

Quindi non si può modificare il classpath e risolvere il problema? No! il problema sta proprio qui, il classloader di sistema viene inizializzato proprio all’inizio della sequenza di avvio e copia il in esso classpath. Quindi, non servirebbe modificare dinamicamente il classpath visto che il classloader di sistema avrebbe già letto il classpath “vecchio” e caricato le classi di conseguenza.

C’è una soluzione a tutto cio?…. Sì! i class loaders … Ma prima entrare nel dettaglio, facciamo una veloce disamina dei class loaders.

Il caricamento di una classe è gestito dai class loaders della JVM, il Bootstrap loader è il loader principale che carica tutte le classi Java di base durante la fase di avvio, come molti sviluppatori Java già sanno non ci sono passi separati di linking come avviene in altri linguaggi di programmazione (vedi linguaggio C).

Quando la JVM carica una classe, utilizzando un class loader, vengono eseguite moltissime operazioni durante la fase di “linking”, tutte le operazioni come: decodifica del formato binario, check di compatibilità, verifica della sequenza delle operazioni e costruzione dell’istanza della classe java.lang.Class verranno gestite dalla JVM stessa.

Queste funzionalità in Java permettono un’elevatissima flessibilità nel caricamento delle classi a “run-time” ma nello stesso tempo aggiungono molto “overhead” al caricamento iniziale delle classi.

Detto ciò, il bootstrap non è l’unico class loader della JVM, come menzionato prima, esiste anche un class loader di sistema che carica tutte le classi dal classpath generale e il quale carica anche tutte le classi dell’applicazione.

Oltre a questi, Java fornisce anche una funzionalità con la quale le applicazioni possono definire i propri class loader, ciascuna classe costruita dal class loader è di proprietà del loader stesso.

In questo articolo cercherò di spiegare come sviluppare un proprio class loader, come aggiungere uno o più JAR al system class loader e le cose che ci sono da comprendere prima di utilizzare un qualsiasi approccio…

Iniziamo con l’importare alcune classi che ci occorrono nel nostro class loader…

import java.net.URL;
import java.net.URLClassLoader;

Abbiamo bisogno di un Uniform Resource Locator (URL) per puntare il nostro JAR, lo creeremo in questa maniera:

URL myJarFile = new URL(“jar”,”",”file:”+myfile.getAbsolutePath()+”!/”);

Il primo argomento è il protocollo che dovrà essere utilizzato per reperire i dati, sono supportati diversi protocolli come: http, https, file, ftp e jar. Il secondo argomento è l’host dove risiedo i dati che stiamo reperendo ed il terzo argomento è la posizione da cui reperire i dati, questa è l’istruzione che definisce il puntamente al JAR.

Possiamo aggiungere questa URL al class loader facendo in modo che tutte le classi contenute nel JAR possano essere utilizzate. Possiamo anche definire un nuovo class loader o ottenere l’istanza del system class loader e aggiungere la URL creata ad esso, comunque mostrerò entrambi gli approcci.

Il metodo getSystemClassLoader() può essere utilizzato per ottenere l’istanza dell’oggetto relativo al System class loader

URLClassLoader sysLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();

Per aggiungere un JAR al system class loader abbiamo bisogno di aggiungere la URL all’oggetto sysLoader, una volta ottenuto il system class loader abbiamo bisogno di ottenere il metodo dichiarato per invocarlo, come mostrato di seguito:

Class sysClass = URLClassLoader.class;
Method sysMethod = sysClass.getDeclaredMethod(“addURL”
                              ,new Class[] {URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{myJarFile});

Ogni oggetto fornisce la possibilità di accedere ai metadati di base della classe come: il package in cui è contenuta, la sua superclasse, tutte le interfacce che implementa, costruttori, metodi, etc..

Come abbiamo visto in precedenza, prima ho provato ad ottenere l’istanza del metodo addURL che è dichiarata nella classe URLClassLoader provando poi il metodo utilizzando il file JAR come un argomento. Lo scopo di questo metodo è aggiungere la URL al system class loader il quale renderà disponibili tutte le classi della URL all’applicazione.

L’utilizzo del system class loader può essere appropriato per applicazioni semplici, ma per applicazioni complesse, come gli application servers, dove non si vuole che un’applicazione interferisca con altre, è importante definire un class loader per ciascuna applicazione.

JAVA consente di derivare i class loaders dalla classe java.lang.ClassLoader, ciascuna classe ha un riferimento a questa classe parent, così facendo ogni volta che un class loader prova a caricare una classe, viene controllato se il parent l’ha già caricata, così qualsiasi classe caricata da un class loader non è visibile solo a se stesso ma anche ma anche a tutti i suoi discendenti. Per default il System class loader è il class loader parent di tutti i class loader definiti dallo sviluppatore.

Adesso per creare un class loader…

URLClassLoader cl = URLClassLoader.newInstance(new URL[] {myJarFile});

Adesso il JAR viene aggiunto al class loader, il prossimo passo è caricare la classe, crearne una istanza, ottenere il metodo di cui abbiamo bisogno ed invocarlo. Assumiamo che la classe sia myclass e che il metodo che abbiamo bisogno di invocare sia “String printMe(String, String)”.

Il sorgente sarebbe così…

Class MyClass = cl.loadClass(“com.mycomp.proj.myclass”);
Method printMeMethod = MyClass.getMethod(“printMe”, new Class[] {String.class, String.class});
Object MyClassObj = MyClass.newInstance();
Object response = printMeMethod.invoke(MyClassObj,”String1″, “String2″);

Cosa bisogna fare se abbiamo bisogno di un costruttore non di default (myclass()) per istanziare myclass ad esempio usando il costruttore myclass(String) ?

Come abbiamo detto prima JAVA ci consente di accedere a tutti i metadati delle classi contenute in un JAR, così per creare una istanza di una classe potremmo aver bisogno del suo costruttore e creare una istanza da esso come mostrato di seguito:

Constructor MyClassConstruct = MyClass.getConstructor(new Class[] {String.class});
Object MyClassObj= MyClassConstructConstruct.newInstance(“myString");

Una volta che l’istanza è stata creata usando il costruttore, possiamo invocare tutti i metodi di questa classe come mostrato in precedenza

Queste funzionalità di JAVA sono un straordinario strumento per costruire codice molto flessibile che può essere esteso a run-time evitando di avere legami rigidi tra le classi.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *