MokaByte Numero 18 - Aprile 1998
di Donato Cappetta e Lorenzo Bettini |
|
In questo articolo vedremo un esempio di class loader personalizzato, in particolare un class loader che carica le classi dalla rete.
La volta scorsa abbiamo visto un'introduzione sul meccanismo di personalizzazione di un class loader in Java.
Questa volta, come già accennato, vedremo un esempio più concreto class loader personalizzato: un NetworkClassLoader, cioè un class loader che scaricherà le classi dalla rete.
Questo tipo di class loader è un esempio di quello che viene detto Codice Mobile.
Codice Mobile
Ultimamente nella programmazione distribuita è sempre più utilizzato il concetto di Computazione Mobile (Mobile Computation) e Codice Mobile (Mobile Code).
L'espressione codice mobile è utilizzata con vari significati in letteratura, ad esempio:
Usualmente ci si riferisce al codice mobile come a del software in grado di viaggiare su una rete eterogenea, attraversando domini di protezione, e che può essere eseguito automaticamente all'arrivo a destinazione. I vantaggi del codice mobile sono molti, fra i quali:
Il codice mobile costituisce una specie di sistema distribuito dove i processi non locali non devono essere conosciuti in anticipo sul sito di esecuzione. Vi è comunque una sostanziale differenza fra un sistema distribuito e la mobilità di codice:
Vi sono già molti esempi di codice mobile, attualmente in uso; anche se non venivano considerati tali, alcuni di essi venivano utilizzati molto prima dell'esplosione di Internet:
L'idea del NetworkClassLoader
Il NetworkClassLoader presentato nell'articolo permette di tenere memorizzate le classi su un unico server; le varie applicazioni (client) possono richiedere il caricamento di una classe tramite il NetworkClassLoader.
La volta scorsa avevamo visto che Java permette di personalizzare il meccanismo tramite il quale vengono caricate in memoria le classi utilizzate da un'applicazione: il class loader.
Java mette a disposizione la classe ClassLoader da cui si può derivare un proprio class loader e ridefinire il metodo loadClass, utilizzato, appunto, per caricare le informazioni di una classe (membri e metodi) in memoria, durante l'esecuzione di un'applicazione:
public Class
loadClass(String className)
throws
ClassNotFoundException {...}
public synchronized Class loadClass(String className, boolean
resolveIt)
throws
ClassNotFoundException {...}
Rivediamo brevemente quello che si deve fare all'interno di questo metodo:
In questo caso il repository personalizzato è la rete: si caricheranno le classi da un server remoto.
Si ricordi che per quanto detto la volta scorsa, tutte le classi che sono necessarie ad una certa classe A sono caricate con lo stesso class loader con cui è stata caricata A. Quindi non ci si dovrà preoccupare di sapere in anticipo le classi necessarie per l'applicazione (classe) che intendiamo scaricare dalla rete: automaticamente il meccanismo di caricamento delle classi di Java, provvederà a caricarle tramite il NetworkClassLoader.
Ed inoltre ogni class loader utilizza un name space diverso e privato: una classe riesce ad accedere solo alle classi caricate con lo stesso class loader; quindi per ogni class loader Java mantiene un name space differente e separato. Proprio per questo motivo, e per il fatto che due classi sono considerate castable, solo se hanno un classe in comune fra le classi parente, una classe caricata col class loader personalizzato deve derivare da una classe (o implementare un'interfaccia) caricata dal file system locale.
Implementazione di un NetworkClassLoader
Affinchè delle classi possano essere caricate dal network è indispensabile, oltre al NetworkClassLoader, la presenza di un programma che si comporti da Server. Occorre un programma che si comporti da contenitore da distributore di classi.
Per l'implementazione di un NetworkClassLoader conviene procedere divedendo l'applicazione in due programmi:
Il
programma Client
Per definire un proprio loader delle
classi occorre ereditare dalla classe java.lang.ClassLoader è definire una implementazione concreta del
metodo astratto
protected abstract Class
loadClass(String name, boolean resolve)
throws
ClassNotFoundException;
I parametri del metodo loadClass() hanno il seguente significato:
In linea generale il comportamento del
metodo loadClass() va definto nel seguente modo:
dato il parametro name rappresentante
il nome di una classe:
Accanto ad ogni operazione è stato
riportato anche il nome del metodo preposto per quel compito.
I metodi elencati, tranne loadClassFromServer(), sono già definiti nella classe java.lang.ClassLoader (JDK1.1), dalla quale si va ad ereditare.
Precisamente questi metodi sono:
Rimane da analizzare il metodo loadClassFromServer() che va implementato.
Il metodo loadClassFromServer()
Il metodo viene
invocato quando una classe, non essendo presente in locale,
deve essere cercata sul network, in particolare sul programma
server ClassServer.
Si suppone che quando
questo metodo viene invocato la connessione con il ClassServer
è stata già stabilita. Cioè si immagini l'esistenza di un
metodo connect()che viene invocato prima del metodo loadClassFromServer() o che viene invocato, una volta per tutte,
direttamente dal costruttore della classe NetworkClassLoader.
Il metodo connect() può essere così implementato:
protected void connect() throws UnknownHostException, IOException { try { socket = new Socket(hostName, serverPort); is = new DataInputStream(new BufferedInputStream(socket.getInputStream())); os = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); } catch(UnknownHostException uhe){ throw uhe; } catch(IOException ioe){ throw ioe; } }
Dove hostName e serverPort sono degli attributi privati della classe,
settati opportunamente dal costruttore.
Analogamente si può immaginare l'esistenza
di un metodo disconnect() che chiude la connessione con il ClassServer:
protected
void disconnect(){
try {
os.close(); os = null;
is.close(); is = null;
socket.close(); socket = null;
} catch(IOException ioe){
} catch (Exception e) {}
}
In che punto del programma invocare il metodo connect() (o il metodo disconnect()) è solo una scelta implementativa.
La logica del metodo loadClassFromServer(), si può così riassumere:
L'array di byte una volta restituito viene trasformato in classe e risolto.
L'implementazione completa del NetworkClassLoader è riportata nel file NetworkClassLoader.java.
Analizzando il listato si osserva che:
Anche l'eccezione ConnectClassServerException è stata appositamente implementata per il loader.
Ci si potrebbe
chiedere del perchè di queste due nuove classi per la gestione
delle eccezioni visto che la libreria di classi Java è
già fornita di una valida gerarchia.
Il perchè è da
ricercare nel modo in cui è stato definito il metodo astratto loadClass()
in java.lang.ClassLoader:
protected abstract Class
loadClass(String name, boolean resolve)
throws
ClassNotFoundException;
si vede che nella clausola throws è
presente solo l'eccezione ClassNotFoundException.
C'è però l'esigenza di gestire anche
condizioni di eccezioni dovute a problemi di connessione con il
server o ad accessi illegali al package Java.
Ora, quando si va a
ridefinire il metodo loadClass() è possibile aggiungere nella clausola throws solo
eccezioni che siano sottoclassi di ClassNotFoundException.
Non è possibile aggiungere eccezioni del
tipo UnknownHostException,
IOException, IllegalAccessException, ecc... altrimenti il codice non viene compilato.
Le classi JavaPackageException e ConnectClassServerException
ereditano da ClassNotFoundException.
Il
programma Server
Il programma Server si occupa di inviare i file .class al NetworkClassLoader. I file vengono inviati come array di byte.
Il programma è composto da due classi:
La
classe ClassServer
E' costituita da un ciclo infinito
che attende delle connessioni su una determinata porta.
Per ogni nuova connessione crea un nuovo
thread WorkerClassServer al quale viene affidata la comunicazione con il
client.
Il blocco di codice principale di questa classe è il seguente:
while (true) {
Socket clientSocket = null;
try {
clientSocket =
serverSocket.accept();
WorkerClassServer
wcs = new WorkerClassServer(clientSocket, classesCache);
wcs.start();
} catch (IOException ioe) {
continue;
} catch(Throwable t){
continue;
}
} //end while()
l'istruzione serverSocket.accept() attende la connessione di un client. Quando
viene stabilita restituisce il socket associato al client: clientSocket.
Dopodichè viene creata una nuova istanza
di WorkerClassServer alla quale viene passato il clientSocket
e l'oggetto classesCache:
WorkerClassServer wcs = new WorkerClassServer(clientSocket, classesCache);
Il thread viene avviato: wcs.start().
L'oggetto classesCache è un'istanza di java.util.Hashtable destinata a funzionare da cache,
per il ClassServer.
Ogni classe richiesta dal NetworkClassLoader, viene aggiunta nella cache
in modo da ottimizzare un successivo ricaricamento.
La cache è stata implementata in modo da
essere globale al programma server, cioè tutti i
thread hanno accesso e aggiornano la stessa cache. Un'altra
soluzione potrebbe essere che ogni thread gestisce una propria
cache; anche in questo caso si tratta di una scelta
implementativa.
La
classe WorkerClassServer
La classe eredita da java.lang.Thread e si occupa di soddisfare le richieste del client. Viene istanziata dalla classe classServer, la quale gli passa il parametro clientSocket per stabilire la connessione con il client, e classesCache il riferimento all'oggetto cache.
La parte principale della classe è
composta da un ciclo infinito inserito nel metodo run().
Definito os e is come stream di input e output associati al clientSocket;
la logica generale del ciclo è la seguente:
Il metodo loadClassFromFile() è da implementare.
Il metodo loadClassFromFile()
Il metodo viene invocato per caricare
una classe dal file system come array di byte.
Dato il nome di una classe nameClass,
la logica del metodo loadClassFromFile() è la seguente:
Il grosso del lavoro viene svolto dal
metodo statico: getSystemResourceAsStream() implementato in java.lang.ClassLoader.
Dato il nome di un file il metodo lo
cercare nel CLASSPATH, se lo trova restituisce un input stream is
associato al file, se non lo trova restituisce null.
L'implementazione completa della classe WorkerClassServer
è riportata nel file WorkerClassServer.java.
Per completare l'applicazione occorre
definire altre due classi: RunServer e RunLoader che si occupano rispettivamente di istanziare il
ClassServer e il NetworkClassLoader.
La classe RunServer contiene il metodo main() con all'interno le istruzioni che istanziano il ClassServer:
ClassServer cs = new ClassServer(port);
cs.start();
Il parametro port rappresenta
la porta di ascolto del Server.
L'istruzione cs.start()
è presente perchè
nell'implementazione, disponibile nei sorgenti, anche la classe ClassServer
eredita dalla classe Thread.
La classe RunLoader contiene il metodo main() con all'interno le seguenti istruzioni
principali:
ClassLoader loader = new
NetworkClassLoader(hostName, port);
Class c = loader.loadClass(mainClass);
Object main = c.newInstance();
dove:
Vanno gestite opportunamente le eccezioni e le condizioni di errore.
Istruzioni per l'uso
Per eseguire il NetworkClassLoader,
occorre aver installato sulla propria macchina una Java Virtual
Machine (JDK o JRE o altre) conforme alla versione 1.1 della SUN.
Dalla directory dove e' contenuto il file RunLoader digitare il
comando:
java RunLoader hostName nameClass
dove hostName e' il nome della
macchina server e nameClass la classe che si vuole
caricare usando il NetworkClassLoader.
Si noti che il NetworkClassLoader cerca prima la classe in
locale, e poi sul server. Quindi la connessione al server verra'
stabilita solo se necessario.
Il nome della classe va digitato senza .class e rispettando
corretamente miuscole e minuscole.
La classe RunLoader applica il metodo newInstance() sulla classe caricata, quindi viene eseguito il costruttore di default di quella classe.
Anche per eseguire il ClassServer,
occorre aver installato sulla propria macchina una Java Virtual
Machine conforme alla versione 1.1 della SUN.
Dalla directory dove e' contenuto il file RunServer digitare il
comando:
java RunServer
Va in esecuzione la classe ClassServer,
che si pone in ascolto di una connessione sulla porta 5050.
Quando il programma viene avviato prova ad individuare il nome (e
l'indirizzo IP) della macchina su cui va in esecuzione. Se questo
non e' possibile viene generata l'eccezione UnknownHostException
.
Quando viene richiesta una classe, viene cercata nel CLASSPATH della macchina su cui il programma e' in esecuzione.
Conclusioni
Alla
base dei Network Computer c'è proprio un loader che quando
necessario scarica le classi dal network.
La possibilita di "centralizzare"
il software su una macchina server, e di avere dei client che si
scaricano l'applicazione quando occorre, rappresenta una
soluzione per la riduzione dei tempi (e dei costi) di
amministazione e manutenzione di un sistema informatico.
Una applicazione verrebbe installata (o
aggiornata) una sola volta sulla macchina server, i client
automaticamente ad ogni riconnessione vedrebbero in esecuzione la
nuova versione installata.
Attualmente gli Applet
tendono già a rappresentare questa filosofia, anche se con delle
limitazioni, imposte per motivi di sicurezza. Per essi esiste un
loader (un AppletClassLoader in genere inserito nel package sun.applet),
che scarica le classi dal network utilizzando il protocollo http e
facendo le richieste ad un programma server che è il server
Web.
Implementando un
NetworkClassLoader è possibile stabilire come gestire la
sicurezza, quale protocollo utilizzare, quale tecnica
utilizzare per scaricare le classi dal network e come
implementare il server in base alle
proprie esigenze e senza nessuna limitazione.
Donato Cappetta
Lorenzo Bettini
[1] Adobe Systems Incorporated. Poscript Language Reference
Manual. Addison-Wesley, 1985
[2] S.J. Cannan, G.A.M. Otten. SQL - The Standard Handbook.
McGraw-Hill, New York, 1992 (edizione italiana: Il manuale SQL,
McGraw-Hill Italia, Milano).
[3] N.S. Boreinstein. Email with a mind of its own. ftp://ftp.fv.com/pub/code/other/safe-tcl.tar.gz
, 1994.
[4] The Java ™ Language Specification. ftp://ftp.javasoft.com/spec
[5] Il Class Loader. Mokabyte
n. 17 - Marzo 1998
Sorgenti