Nell'articolo
precedente è stata descritta l'interfaccia JNDI, caratteristica
dell'ultima versione delle JDK. Questo strumento rappresenta la soluzione
proprietaria Sun che consente la comunicazione fra oggetti in remoto, ponendosi
a metà strada fra RMI e CORBA, come meglio vedremo nel seguito.
La
soluzione JNDI consente di creare facilmente contesti di naming e directory,
e di manipolare gli oggetti creati.
In
questo articolo vedremo un esempio, molto banale, di un client e di un
server che comunicano tramite l'interfaccia JNDI, per evidenziare le poche
istruzioni necessarie per effettuare invocazioni di metodi in remoto.
http://java.sun.com/products/jndi/
è l’indirizzo a cui collegarsi per scaricare i package da installare;
come la maggior parte degli sviluppatori sa, per utilizzare le JDK 1.2.2
standard non è necessario utilizzare la variabile di sistema classpath,
ma serve dichiararla per includere le nuove classi. Per l’esempio che vedremo
i file da includere sono i seguenti: jndi.jar, rmiregistry.jar, e providerutil.jar.
Iniziamo
a vedere il codice del server
package
prova;
public
final class MioServerProva implements java.rmi.Remote, java.io.Serializable
{
La
classe del server, poiché abbbiamo scelto l'implementazione RMI
dell'interfaccia JNDI, deve implementare le interfacce Remote e Serializable.
public
void mioMetodoServer(String param) {
System.out.println("Metodo mioMetodoServer");
System.out.println("Parametro = "+param+"\n\n");
}
Dichiariamo
ora un metodo semplice, in maniera standard, per invocarlo da remoto
public
static void main (String[] args) {
try{
java.lang.Process pRMI =
(java.lang.Runtime.getRuntime()).exec("c:\\jdk1.2.2\\bin\\rmiregistry");
}
Inizia
il main della classe: la prima istruzione è stata inserita per evitare
di lanciare dalla finestra DOS l'rmiRegistry (come si fa per usare RMI)
ad ogni esecuzione del programma.
Con
il metodo getRuntime si ottiene il puntatore all’ambiente di esecuzione,
che consente, tramite il comando exec, di lanciare il comando DOS c:\jdk1.2.2\bin\rmiregistry
in un altro processo, il cui puntatore è memorizzato nella variabile
pRMI: il doppio backslash è ovviamente dovuto al metodo di elaborazione
delle stringhe della JVM.
MioServerProva
mSP=new MioServerProva();
Si
crea un'istanza della classe server
javax.naming.InitialContext
nuovoContesto=null;
Si
crea un'istanza del contesto di naming: come abbiamo specificato nell'articolo
precedente il contesto iniziale deve essere un'istanza della classe InitialContext,
mentre altri eventuali contesti potranno essere istanze della classe Context
java.util.Hashtable
hashtableProprieta = new java.util.Hashtable();
Si
crea un'istanza della classe Hashtable che conterrà le proprietà
del contesto di naming
hashtableProprieta.put(
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.rmi.registry.RegistryContextFactory");
hashtableProprieta.put(
javax.naming.Context.PROVIDER_URL,
"rmi://150.197.40.227:1099");
Si
inseriscono all'interno dell'Hashtable le proprietà del contesto
secondo la solita prassi (chiave,valore).
Per
inizializzare un contesto di naming bisogna fornire la factory e l'URL
del provider: la factory è implementata da una delle classi contenute
nel package delle JNDI scaricato, mentre l'url contiene, in sequenza, il
tipo di comunicazione (rmi), l'indirizzo IP del provider del servizio ed
il numero della porta di comunicazione.
Se
gli oggetti client e server si trovano sulla stessa macchina si può
usare, al posto dell'indirizzo IP, anche la stringa "localhost".
nuovoContesto=
new javax.naming.InitialContext(hashtableProprieta);
Il
nuovo contesto di naming è inizializzato con i parametri descritti
in precedenza: si sfrutta il costruttore che ammette come parametro di
ingresso un oggetto di classe Hashtable.
nuovoContesto.bind("ServerJNDI",mSP);
A questo
punto si effettua il bind dell'oggetto di classe MioServerProva creato
al nuovo contesto, cioè si rende accessibile da altri oggetti, con
il nome "ServerJNDI", l'oggetto creato sull'RMIRegistry.
...
System.out.println("il server Osservatorio e' pronto.");
}
catch(Exception e) {
System.out.println("Server - Eccezione nel main");
e.printStackTrace();
}
}
// end main
}
// end class MioServerProva
A questo
punto il codice del server è terminato.
Passiamo
ora ad esaminare il codice del client
package
prova;
public
final class MioClientProva implements java.rmi.Remote, java.io.Serializable
{
Anche
la classe del client, poiché abbbiamo scelto l'implementazione RMI
dell'interfaccia JNDI, deve implementare le interfacce Remote e Serializable.
public
static void main (String[] args) {
try {
String nomeServer="ServerJNDI";
java.util.Hashtable hashtableProprieta =
new java.util.Hashtable();
...
Inizia
il main: si crea un'istanza della classe Hashtable che conterrà
le proprietà del contesto di naming
hashtableProprieta.put(
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.rmi.registry.RegistryContextFactory");
hashtableProprieta.put(
javax.naming.Context.PROVIDER_URL,
"rmi://150.197.40.227:1099");
javax.naming.InitialContext
contestoDiNaming=
new javax.naming.InitialContext(hashtableProprieta);
Queste
istruzioni sono le stesse del codice del server, e servono per creare il
riferimento al contesto creato con il server: da notare che è necessario
(come per RMI) conoscere l’indirizzo IP della macchina sulla quale è
in esecuzione l’oggetto remoto
MioServerProva
rifServer=
(MioServerProva)contestoDiNaming.lookup(nomeServer);
A questo
punto si effettua il lookup al server tramite il contesto con il nome "ServerJNDI":
il metodo restituisce un oggetto (come RMI normale?) sul quale si effettua
il cast alla classe desiderata.
rifServer.mioMetodoServer("parametro
di prova");
Questa
è l'invocazione del metodo in remoto
System.out.println("invocazione remota eseguita\n");
}
catch(Exception e) {
System.out.println("Client - Eccezione nel main");
e.printStackTrace();
}
}
// end main
}
// end class MioClientProva
A questo
punto è terminato anche il codice del client
Le
differenze con la comunicazione RMI, come si evince dal codice, sono poche:
adesso vediamo come modificare il codice del main del server per poter
sfruttare le nuove funzionalità offerte da JNDI
public
static void main (String[] args) {
try{
java.lang.Process pRMI =
(java.lang.Runtime.getRuntime()).exec(
"c:\\jdk1.2.2\\bin\\rmiregistry");
MioServerProva mSP=new MioServerProva();
MioServerProva mSP2=new MioServerProva();
Si
creano due istanze da pubblicare nel contesto
javax.naming.InitialContext
nuovoContesto=null;
java.util.Hashtable
hashtableProprieta =
new java.util.Hashtable();
hashtableProprieta.put(
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.rmi.registry.RegistryContextFactory");
hashtableProprieta.put(
javax.naming.Context.PROVIDER_URL,
"rmi://150.197.40.227:1099");
nuovoContesto=
new javax.naming.InitialContext(hashtableProprieta);
nuovoContesto.bind("ServerJNDI",mSP);
nuovoContesto.bind("ServerJNDI2",mSP2);
System.out.println("il
server Osservatorio e' pronto.\n");
//
nome-classe
javax.naming.NamingEnumeration
listaOggetti =
nuovoContesto.list(nuovoContesto.getNameInNamespace());
System.out.println("lista
degli oggetti inseriti nel
contesto");
while
(listaOggetti.hasMore()) {
javax.naming.NameClassPair nc =
(javax.naming.NameClassPair)listaOggetti.next();
System.out.println(nc);
}
Si
ottiene l’elenco degli oggetti pubblicati nel contesto
//
nome-classe-oggetto
javax.naming.NamingEnumeration
listaBindings = nuovoContesto.listBindings(nuovoContesto.getNameInNamespace());
System.out.println("\nlista
dei binding del contesto");
while
(listaBindings.hasMore()) {
javax.naming.Binding bd =
(javax.naming.Binding)listaBindings.next();
System.out.println(bd.getName() + ": " + bd.getObject());
}
Si
ottiene l’elenco degli oggetti pubblicati nel contesto con i relativi riferimenti
nuovoContesto.unbind("ServerJNDI");
nuovoContesto.rename("ServerJNDI2","ServerJNDI2nuovo");
//
nome-classe
listaOggetti
= nuovoContesto.list(nuovoContesto.getNameInNamespace());
System.out.println("\nlista
degli oggetti inseriti nel contesto dopo l'unbind ed il rename");
while
(listaOggetti.hasMore()) {
javax.naming.NameClassPair nc =
(javax.naming.NameClassPair)listaOggetti.next();
System.out.println(nc);
}
Eliminiamo
un oggetto dal contesto, rinominiamo l’altro e controlliamo sull’elenco
cos’è accaduto
nuovoContesto.close();
pRMI.destroy();
Chiudiamo
il contesto ed eliminiamo il processo che esegue l’rmiregistry, che non
servono più. In questo caso, ovviamente, non è più
possibile eseguire la chiamata remota dal client.
}
catch(Exception e) {
System.out.println("Eccezione nel main");
e.printStackTrace();
}
}
// end main
Quella
che segue è la schermata che ho ottenuto compilando ed eseguendo
il secondo sorgente del server:
La
comunicazione RMI pura ormai è sempre più raramente utilizzata
per le soluzioni, a causa delle limitate possibilità che offre,
anche se, come ha dimostrato Giovanni Puliti nel suo articolo nel numero
del mese precedente, combinando questa tecnica con l’utilizzo dei Design
Pattern se ne possono ampliare le funzionalità.
Con
l’architettura JNDI Sun offre al programmatore un numero elevato di funzionalità
a “costo zero”, cioè semplicemente importando le classi opportune:
il codice descritto ne ha presentate solo alcune, ma leggendo l’help del
package se ne scoprono tantissime, soprattutto per quanto riguarda la gestione
con provider LDAP (in questo senso il tutorial indicato nella documentazione
è pieno di esempi). L’innovazione principale, a mio modo di vedere,
sta nel fatto che non devono più essere gestiti stub e skeleton,
che invece sono utilizzati sia per RMI sia per CORBA
Anche
con un’architettura CORBA si possono ottenere gli stessi risultati, ma
personalmente la ritengo molto più pesante da gestire rispetto a
JNDI, che è una soluzione proprietaria: ne consiglio l’utilizzo
solo in caso di necessità, se cioè siamo costretti ad inglobare
moduli non sviluppati in Java e a non poter usare JNI. In caso contrario,
la soluzione JNDI è sicuramente più semplice da usare, e
sotto certi aspetti fornisce anche qualche funzionalità in più,
che o non è fornita da CORBA (come l’integrazione con provider di
tipo LDAP), o è fornita come servizio aggiuntivo da integrare (come
il servizio di naming).
Conclusione
I
due articoli presentati non vogliono essere una spiegazione esaustiva dell’argomento
in esame, che meriterebbe ben altra trattazione. Essi sono una semplice
introduzione, che ha lo scopo di presentare questo nuovo strumento di programmazione
nelle sue caratteristiche generali: sarà poi compito dello sviluppatore
esplorarne nel dettaglio le funzionalità, ed adattarle al problema
da risolvere.
Bibliografia
Il
sito della Sun e’ una fonte insostituibile di documentazione, in particola
sull’argomento si puo’ consultare il seguente tutorial:
http://java.sun.com/products/jndi/tutorial/trailmap.html
Mini Biografia
Laureato
a Napoli alla facolta’ Federico II in Ingegneria Informatica nel 1999,
ho partecipato al master in “Internet Software Design” tenuto presso l’istituto
Cefriel di Milano nello stesso anno (da maggio a dicembre): da gennaio
lavoro presso la Siemens Informatica S.p.A. di Roma come Analista e System
Integrator.
|