MokaByte Numero  42  - Giugno 2000
L'interfaccia JNDI 
del JDK 1.2
II parte
di 
D'amore 
Salvatore
In questo articolo è mostrato un piccolo esempio client/server per evidenziare le principali innovazioni dell’architettura JNDI

L'evoluzione sempre piu' spinta dei modelli architetturali distribuiti (DCOM, CORBA) fa sentire con crescente insistenza l'esigenza di una comunicazione efficiente fra i moduli della stessa applicazione: Sun con l'interfaccia JNDI propone una soluzione proprietaria per la comunicazione, altamente innovativa per la sua versatilita'

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.
 

Chi volesse mettersi in contatto con la redazione può farlo scrivendo a mokainfo@mokabyte.it