|
Java: Arte e tecnica
Come si scrive un'applicazione RMI
Dario de Judicibus
Una delle caratteristiche di Java Φ quella di rendere semplice
la scrittura di applicazioni distribuite in rete, cosa che solitamente provoca un vero e
proprio "mal di stomaco" anche ai programmatori professionisti. Vediamo come
attraverso un semplice esempio
Lo scorso numero abbiamo scritto che l’RMI Φ
un’interfaccia Java che permette di invocare un metodo di un oggetto remoto.
L’invocazione Φ del tutto equivalente a quella che possiamo fare dei metodi di un
qualunque oggetto locale. Θ quindi possibile passare uno o pi∙ parametri al metodo in
questione, cos“ come ricevere un’eventuale risposta e persino un’eccezione.
Abbiamo inoltre affermato che l’intero meccanismo Φ molto semplice da usare, dato
che scherma il programmatore dai dettagli relativi allo scambio e alla gestione dei dati
coinvolti. Adesso vogliamo dimostralo presentando una piccola applicazione, scritta
appositamente per la nostra testata, che si prefigge tre obiettivi: descrivere in modo
dettagliato tutti i passi necessari per sviluppare un’applicazione basata su RMI;
fornire un comodo scheletro di lavoro su cui potrete sviluppare le vostre applicazioni e,
terzo obiettivo, far vedere uno dei possibili vantaggi di questo tipo di applicazioni
rispetto, per esempio, a interfacce applet ╟classiche╚ verso applicazioni remote. Per
far tutto questo procederemo in modo professionale: definiremo cioΦ prima le specifiche
dell’applicazione, poi il disegno, e quindi il codice. Anche in questo caso vi
consigliamo di stampare il codice presente nel CD-ROM prima di proseguire la lettura.
Le specifiche
Una caratteristica di Java Φ quella legata al concetto di proprietα.
Una proprietα altro non Φ che una coppia chiave/valore; un insieme di coppie forma una lista
di proprietα e la lista pu≥ essere salvata in un file, acquisendo cos“ un
carattere di persistenza; pu≥ inoltre essere associata a un oggetto o a un componente del
sistema. In Java esiste una particolare lista di proprietα chiamata Proprietα del
Sistema che contiene una serie d’informazioni disponibili alle applicazioni e
relative al sistema su cui girano. L’approccio Φ simile a quello usato per le
cosiddette variabili di sistema o variabili d’ambiente che esistono in
pratica un po’ in tutti i sistemi operativi. Al contrario di queste ultime, tuttavia,
le proprietα del sistema sono le stesse in tutte le piattaforme su cui gira Java, anche
se, ovviamente, esse prenderanno valori diversi nei differenti sistemi. La lista completa
di tali proprietα Φ riportata nella tabella 1, con un esempio del tipo di valore che
ciascuna proprietα pu≥ prendere, e un marcatore che indica se la proprietα in questione
Φ accessibile via applet (~ ) o meno.
Per ragioni di sicurezza, infatti, Java non
permette a un’applet di accedere ad alcune informazioni critiche del sistema su cui
Φ in esecuzione. Questo evita che, per esempio, caricando una pagina via Internet, il
gestore delle pagine acquisisca alcune informazioni sul vostro sistema a vostra insaputa
utilizzando un’apposita applet. Ma se io volessi scrivere un’applicazione che
volutamente esporta verso l’esterno determinate informazioni?
Proviamo a scrivere un’applicazione del genere, un programma
cioΦ che mette a disposizione di un altro programma operante su un sistema remoto tutte
le proprietα del sistema locale. Il programma in questione dovrα, a fronte di una
richiesta remota, restituire il valore della proprietα di cui le sarα stato fornito il
nome. Nel caso in cui la proprietα richiesta non esistesse verrα restituito un messaggio
di errore. Sarα inoltre possibile richiedere a priori se la proprietα in questione Φ
supportata o meno.
Il disegno
Per far questo dobbiamo prima di tutto scrivere una classe che,
data una proprietα, ne ritorni il valore; la chiameremo classe SystemProperty. Scriveremo
quindi un programma di collaudo della classe in questione che denomineremo ShowProperty.
Una volta verificato il corretto funzionamento delle due classi, inizieremo a costruire
quelle per l’accesso remoto. Avremo in tutto altre quattro classi. La prima,
RemoteProperty, rappresenta la proprietα del sistema remoto, e non Φ in realtα una
classe, ma l’interfaccia d’invocazione dell’oggetto remoto, cioΦ la
definizione dei metodi che potranno essere invocati remotamente. La seconda,
RemotePropertyImpl, rappresenta l’oggetto i cui metodi saranno invocati remotamente,
ed Φ basata sull’interfaccia definita in precedenza. La terza, RemotePropertyServer,
Φ l’applicazione remota che crea un oggetto della classe RemotePropertyImpl e lo
registra sotto la propria URL. In pratica Φ come se il server affermasse ╟Mi chiamo
in un certo modo. La URL attraverso la quale potete accedere ai miei servizi Φ questa, e
l’oggetto in questione pu≥ essere esportato remotamente attraverso di me╚. La
quarta classe, ShowRemoteProperty, Φ l’applicazione che richiede al sistema remoto
una determinata proprietα e ne visualizza il valore sul sistema locale. Ricordiamo che
qui, sia i concetti di locale e di remoto sia quelli di servente e di cliente sono del
tutto intercambiabili. Le ultime due classi sono generate automaticamente con il comando
rmic, e rappresentano rispettivamente la matrice (RemotePropertyImpl_Stub) che si occupa
della serializzazione dei dati, e lo scheletro (RemotePropertyImpl_Skel) che interpreta la
richiesta e restituisce il valore della proprietα al cliente.
Il codice
E veniamo ora al codice. Iniziamo con la classe SystemProperty,
ovverosia con la classe il cui compito Φ restituire il valore di una proprietα di cui le
sia stato fornito il nome. Θ una classe molto semplice: dato che non c’Φ alcun
motivo di creare un oggetto per ottenere il valore in questione, scriveremo una classe
statica, cioΦ una classe i cui metodi e variabili sono tutte statiche.
L’acquisizione vera e propria del valore sarα ottenuta con l’istruzione
value = System.getProperty( propertyName ) ;
La classe verifica inoltre che la proprietα richiesta sia tra
quelle supportate. Per semplicitα ci≥ avviene utilizzando un vettore statico. Tale
approccio non Φ ottimale, in quanto il giorno in cui si dovessero aggiungere altre
proprietα di sistema a quelle attuali, bisognerα aggiornare la lista in questione. Se
volete, come esercizio potete provare a vedere come ottenere tale lista direttamente dal
sistema.
La classe per il collaudo di SystemProperty Φ ancora pi∙
semplice. In pratica non fa altro che ricevere il nome della proprietα in ingresso e
restituirne il valore in uscita utilizzando appunto la classe suddetta:
String propertyValue = SystemProperties.getValue(
propertyName ) ;
Per esempio:
[I:\devel\Java\RMI] java ShowProperty os.name
Property 'os.name' has value 'Windows 95'.
A questo punto siamo pronti per sviluppare l’applicazione
RMI. Per prima cosa definiamo l’interfaccia, come estensione di java.rmi.Remote.
Questa conterrα un solo metodo:
public String getValue( String propertyName )
throws java.rmi.RemoteException ;
Da notare l’eccezione che va sempre riemessa qualunque sia il
metodo e qualunque cosa faccia. Stiamo infatti effettuando comunque un’operazione via
rete, per cui non si pu≥ mai sapere: la linea pu≥ cadere, essere intasata, o comunque i
dati possono perdersi o rovinarsi per un qualunque motivo. Fatto questo ne scriviamo
l’implementazione, definiamo cioΦ una classe RemotePropertyImpl che estende
UnicastRemoteObject e implementa l’interfaccia RemoteProperty. Anche questa classe Φ
molto semplice, essendo formata da un costruttore che non fa altro che richiamare il
costruttore della superclasse, e dal metodo getValue che contiene solamente
return SystemProperties.getValue( propertyName ) ;
Per finire, le due classi pi∙ complicate. Prima il servente.
Questi contiene un metodo statico principale (main) che per prima cosa crea e installa un
gestore di sicurezza, in modo da evitare che siano esportate classi diverse da quelle
previste dall’applicazione (vedi puntata precedente), quindi costruisce l’URL
per mezzo della quale il servente Φ acceduto, e infine registra l’oggetto da
invocare remotamente. L’URL base va fornita quando si lancia il servente, altrimenti
si assume che cliente e servente siano sullo stesso sistema e che quindi questa sia nulla.
A tale stringa il programma aggiunge l’identificativo dell’oggetto remoto, un
nome cioΦ che identifica univocamente l’oggetto RemotePropertyImpl associato al
servente di cui Φ stata fornita l’Url base, nel nostro caso ╟RemoreProperty╚.
Questo in quanto lo stesso servente pu≥ gestire pi∙ oggetti. Attenzione per≥: se volete
collaudare questo programma lanciando sia il cliente sia il servente sulla stessa
macchina, dovrete assicurarvi comunque di essere in rete, cioΦ che il TCP/IP sia attivo.
Se fate questo su una macchina connessa a una rete locale (LAN) via TCP/IP, non c’Φ
problema. Se invece siete a casa sul vostro PC, dovrete collegarvi a Internet via modem
per far funzionare il tutto. La registrazione avviene utilizzando la classe Naming, ovvero
RemotePropertyImpl obj = new RemotePropertyImpl( ) ;
Naming.rebind( serverUrl, obj );
Per quanto riguarda il cliente, all'inizio anch'esso installerα
il gestore di sicurezza RMI, cioΦ
System.setSecurityManager( new RMISecurityManager( )
) ;
quindi costruirα l’Url del servente e lo userα per
agganciare l’oggetto remoto a esso associato. A questo punto il gioco Φ fatto:
basterα utilizzare l’oggetto remoto come se fosse un oggetto locale e ricavare il
valore della proprietα come avevamo fatto in precedenza con ShowProperty:
RemoteProperty ps = (RemoteProperty)Naming.lookup(
serverUrl ) ;
String propertyValue = ps.getValue( propertyName ) ;
Compilazione ed esecuzione
A questo punto ci ritroviamo con sei sorgenti:
ShowRemoteProperty.java
RemotePropertyServer.java
RemotePropertyImpl.java
RemoteProperty.java
ShowProperty.java
SystemProperties.java
che compileremo lanciando javac su ognuno dei sei sorgenti, e
quindi rmic su RemotePropertyImpl. Il risultato saranno otto file di tipo .class,
ovverosia
ShowRemoteProperty.class
RemoteProperty.class
ShowProperty.class
SystemProperties.class
RemotePropertyServer.class
RemotePropertyImpl.class
RemotePropertyImpl_Stub.class
RemotePropertyImpl_Skel.class
Carichiamo sul sistema locale
l'applicazione cliente (ShowRemoteProperty.class)
l'interfaccia (RemoteProperty.class)
la matrice (RemotePropertyImpl_Stub.class)
mentre sul sistema remoto mettiamo:
l'applicazione servente (RemotePropertyServer.class)
l'interfaccia (RemoteProperty.class)
l'implementazione (RemotePropertyImpl.class)
lo scheletro (RemotePropertyImpl_Skel.class)
Adesso siamo pronti. Sul servente lanciamo il registro RMI in un
processo a parte. Ad esempio, in Windows 95 eseguiremo
start rmiregistry
mentre in UNIX il comando da usare Φ
rmiregistry &
Sempre sul servente facciamo partire l'applicazione che gestirα
le richieste del cliente, e cioΦ
start java RemotePropertyServer (Windows 95)
java RemotePropertyServer & (UNIX)
Spostiamoci ora sul cliente e lanciamo, per esempio
[F:\test\Java\RMI] java ShowRemoteProperty os.name
Property 'os.name' has value 'Windows 95'
Semplice, no?
(ddejudicibus@tecnet.it)
TABELLE
Proprietα
|
Descrizione
|
Esempio
|
~
|
Java.version
|
Attuale versione del linguaggio Java
|
11
|
s∞
|
Java.vendor
|
Stringa identificativa del produttore del
linguaggio
|
Sun Microsystems Inc.
|
s“
|
Java.vendor.url
|
Url del produttore del linguaggio
|
http://www.sun.com/
|
s“
|
Java.home
|
Directory d’installazione di Java
|
d:\java
|
no
|
Java.class.version
|
Attuale versione del formato delle classi Java
|
45.3
|
s“
|
Java.class.path
|
Cammino di ricerca per le classi Java
|
.;d:\java\bin;
|
no
|
Os.name
|
Nome del sistema operativo
|
Windows 95
|
s“
|
Os.arch
|
Architettura del sistema operativo
|
x86
|
s“
|
Os.version
|
Attuale versione del sistema operativo
|
4.0
|
s“
|
File.separator
|
Separatore dei componenti del nome del file
|
\
|
s“
|
Path.separator
|
Separatore dei vari cammini di ricerca
|
;
|
s“
|
Line.separator
|
Separatore di lista
|
\n
|
s“
|
User.name
|
Nome dell'utente
|
pippo
|
no
|
User.home
|
Directory principale dell'utente
|
d:\java\bin\..
|
no
|
User.dir
|
Attuale directory di lavoro dell'utente
|
i:\devel\java\rmi
|
no
|
Tabella 1 - Proprietα del sistema
Letti per voi
Se cercate un buon libro che affronti la programmazione in Java in
modo semplice ma abbastanza completo, vi consigliamo The Java Tutorial di Mary
Campione e Kathy Walrath, edito dalla Addison-Wesley e facente parte della collana The
Java Series. Il volume, di oltre 400 pagine, copre un po’ di tutto, dai
princ“pi della programmazione orientata agli oggetti alle applet fino a descrive le
principali classi base del Java Development Kit, dalle interfacce grafiche alla
programmazione in rete con perfino un accenno all’integrazione di metodi nativi in
programmi Java. La versione in nostro possesso Φ un po’ datata, ma Φ di recente
uscita la seconda edizione che copre fino al JDK 1.2. L’aspetto interessante di
questo libro Φ che le autrici propongono vari cammini alternativi di lettura ognuno
indicato per esigenze di programmazione differenti. Per cui, per esempio, chi Φ
interessato a scrivere solo applet, pu≥ saltare a piΦ pari il capitolo sui metodi
nativi, mentre chi Φ pi∙ interessato alle applicazioni, pu≥ tranquillamente non leggere
la sezione che parla di applet. Entrambe le autrici sono scrittrici professioniste di
manuali (technical writer) e fanno parte del team JavaSoft di Sun. Il libro
Φ di facile lettura, pieno di esempi, e corredato da un utile CD-ROM contenente
l’ultima versione del JDK, moltissimi esempi di ottimo codice e diversi prodotti per
lo sviluppo in Java.
Il codice ISBN Φ 0-201-31007-4.
Forse non tutti sanno che...
Se volete comparare due stringhe, non utilizzate mai
l’operatore ==, ma i metodi di String equals() oppure compareTo(). Il motivo Φ che
una variabile di tipo stringa Φ in effetti una referenza alla stringa vera e propria, e
non il suo contenuto. Per cui, quando scrivete il codice
if ( compiler == "VisualCafΘ" )
{
System.println( "Symantec" ) ;
}
state comparando il contenuto di due puntatori, e non le due
stringhe di caratteri. Ecco perchΘ se anche compiler contenesse la stringa
"VisualCafΦ", il codice non stamperebbe la scritta "Symantec". In
generale utilizzando l'operatore == state verificando che i due oggetti siano in realtα
lo stesso oggetto, non che abbiano lo stesso contenuto.
Ma allora perchΘ il seguente pezzo di codice funziona?
1| String compiler = "VisualCafΘ" ;
2|
3| if ( compiler == "VisualCafΘ" )
4| {
5| System.println( "Symantec" ) ;
6| }
Il motivo Φ semplice. Per motivi di ottimizzazione, tutte le
stringhe di tipo literal, cioΦ fissate a priori esplicitamente, che abbiano lo
stesso contenuto, fanno riferimento allo stesso oggetto. In pratica il compilatore si
accorge che ci sono due stringhe uguali, una alla riga 1 e una alla riga 3. Dato che si
tratta di stringhe esplicite, e non di variabili, esse non possono essere modificate, per
cui genera un solo oggetto il cui valore Φ "VisualCafΦ". A questo punto, a
seguito dell’istruzione 1, alla variabile compiler viene assegnato il ╟puntatore╚ a
tale oggetto, che poi Φ lo stesso della stringa alla riga 3, per cui la condizione di
uguaglianza Φ soddisfatta. Attenzione per≥: il fatto che il secondo blocco di codice
funzioni non vi autorizza a usarlo. Sarebbe un errore che si potrebbe pagare in seguito.
Se infatti in futuro decideste di acquisire il valore di compiler dall’esterno,
magari passato come parametro, e se fra tale assegnazione e la comparazione ci fosse molto
codice, nel momento in cui il programma non dovesse pi∙ funzionare ci potreste perdere
vari giorni prima di capire perchΘ. Cercate quindi di scrivere sempre un codice corretto
e, nel caso delle stringhe, usate sempre gli appositi metodi e vedrete che non avrete
problemi |