XII - RMI, JavaBeans et JDBC

 

Dans le dernier chapitre de cette partie, nous allons voir quelques composants ou caractéristiques nouvellement introduits dans Java, n'ayant pas forcément de rapport entre eux, à savoir le mécanisme d'invocation de méthodes distantes (RMI), les JavaBeans et enfin l'API permettant à un programme Java d'accéder à une base de données.

 

1. Remote Method Invocation

RMI ou Remote Method Invocation est un mécanisme qui permet d'appeler, dans un programme Java, une méthode se trouvant sur une machine distante et ce, de façon totalement transparente. En clair, on construit l'application comme si la méthode était localement disponible, en lui passant des arguments si besoin est. Ensuite, quand on exécute l'application, il va y avoir transmission de l'appel de la méthode à un serveur distant qui aura pour but d'exécuter cette méthode et de renvoyer le résultat de l'exécution à l'application demandeuse.

Un tel système permet de centraliser certains développements et d'exécuter des calculs, opération parfois coûteuse en ressources, sur une machine distante. RMI masque la présence d'un réseau si bien que le programmeur n'a pas à connaître la moindre notion de programmation réseau.

Voici la structure générale du système RMI :

On voit que deux applications sont impliquées : un client et un serveur.

Le système RMI comporte trois couches : stub/squelette, couche de référence distante et Transport.

Lorsque le client demande l'exécution d'une méthode distante, il s'adresse au stub qui est une implémentation locale des interfaces des objets distants. Ce stub transmet la requête à la couche de référence distante (Remote Reference Layer). Cette couche fait l'interfaçage entre le stub et la couche de transport sous-jacente et contient des informations spécifiques au serveur distant. Elle transmet et reçoit des informations de la couche de transport via une connexion orientée flux. La couche de transport a pour but de gérer les connexions réseau entre les couches de référence distantes client et serveur. En remontant vers le serveur on traverse les mêmes couches sauf celle se trouvant en contact avec le serveur, le squelette, qui a pour objet de distribuer les appels des clients vers les implémentations fournies par le serveur.

Les couches de stub et squelette sont générées par le compilateur rmic, comme nous le verrons plus loin. Ce compilateur génère deux classes qui seront chargées dynamiquement à l'exécution, lorsque c'est nécessaire.

Afin d'illustrer ce mécanisme, nous allons prendre un exemple très classique, consistant à utiliser une applet qui va appeler une méthode, renvoyant la chaîne "Hello World!", et se trouvant sur un serveur distant.

Pour ce faire, nous allons créer quatre fichiers :

  1. un fichier de définition de l'interface, Hello.java,
  2. une implémentation de cette interface, sous forme de serveur (HelloImpl.java),
  3. une applet appelant la méthode définie sur le serveur précédent (HelloApplet.java),
  4. un fichier HTML lançant l'applet (index.html).

On disposera tous ces fichiers dans un répertoire hello, ayant pour père rmidemo.

 

Définition de l'interface

Voici le code de l'interface :

// Hello.java

//

package rmidemo.hello;

public interface Hello extends java.rmi.Remote {

String sayHello() throws java.rmi.RemoteException;

}

Ce fichier se trouve donc dans le package rmidemo.hello, et contient la définition d'une interface nommée Hello, qui doit obligatoirement être déclarée publique (sinon il ne serait pas possible de l'appeler à distance) et qui hérite de l'interface Remote du package java.rmi. On indique dans cette interface la signature de la fonction distante, à savoir sayHello(), qui ne prend pas d'argument mais qui renvoie une chaîne de caractères. La signature de cette méthode doit impérativement signaler qu'elle est susceptible de lever une exception du type java.rmi.RemoteException, en cas de problème durant l'appel ou l'exécution de la méthode.

 

Ecriture du serveur

Le serveur est un programme à part entière qui a pour rôle de répondre aux demandes d'exécution de la méthode définie dans la classe qu'il implémente. C'est une application qui fournit un service à une application distante. Voici le code de ce serveur :

// HelloImpl.java

//

package rmidemo.hello;

import java.rmi.*;

import java.rmi.server.UnicastRemoteObject;

public class HelloImpl

extends UnicastRemoteObject

implements Hello

{

private String name;

public HelloImpl(String s) throws RemoteException {

super();

name = s;

}

public String sayHello() throws RemoteException {

return "Hello World!";

}

public static void main(String args[])

{

// Crée et installe un security manager

System.setSecurityManager(new RMISecurityManager());

try {

HelloImpl obj = new HelloImpl("HelloServer");

Naming.rebind("//localhost/HelloServer", obj);

System.out.println("HelloServer enregistré dans le registry");

} catch (Exception e) {

System.out.println("HelloImpl err: " + e.getMessage());

e.printStackTrace();

}

}

}

Analysons ce source. On indique tout d'abord que l'on implémente la classe Hello que nous venons de définir. La classe ainsi définie, HelloImpl, hérite de la classe serveur UnicastRemoteObject qui correspond à un serveur simple, utilisant les mécanismes de transport par défaut.

On définit ensuite le constructeur de cette classe qui ne fait rien d'autre que d'initialiser une donnée membre contenant le nom du serveur.

Vient ensuite l'implémentation de la méthode distante qui constitue le service proposé par le serveur. A noter qu'un même serveur peut proposer plusieurs méthodes. Cette fonction, sayHello(), retourne la chaîne "Hello World!". On précise par ailleurs que durant l'exécution de cette méthode, sur demande d'une application client distante, il peut se produire une erreur d'où la levée d'une exception. Il est important de remarquer que, lorsqu'une telle méthode a des arguments, les données locales sont passées par valeur tandis que les objets distants sont passés par référence.

On arrive alors dans la fonction main() du serveur, dans laquelle on crée et lance un security manager ou gestionnaire de sécurité de façon à permettre le chargements de classes RMI. Ensuite, on crée un objet de type HelloImpl, c'est une instance de la classe fournissant le service désiré. On enregistre alors l'objet distant auprès d'une base appelée registry permettant ainsi à des applications clientes d'exécuter une méthode de cet objet distant. Cet enregistrement établit une correspondance entre l'objet et une URL permettant d'accéder à cet objet à distance : Naming.rebind("//localhost/HelloServer", obj);. La méthode rebind() de la classe Naming du package java.rmi permet d'effectuer cette opération.

 

Ecriture de l'applet utilisant le service distant

Il nous reste à présenter le source de l'applet appelant la méthode distante sayHello() dont nous venons de présenter l'implémentation :

// HelloApplet.java

//

package rmidemo.hello;

import java.awt.*;

import java.rmi.*;

public class HelloApplet extends java.applet.Applet {

String message = "";

public void init() {

try {

Hello obj = (Hello)Naming.lookup("//" +

getCodeBase().getHost() + "/HelloServer");

message = obj.sayHello();

} catch (Exception e) {

System.out.println("HelloApplet exception: " +

e.getMessage());

e.printStackTrace();

}

}

public void paint(Graphics g) {

g.drawString(message, 25, 50);

}

}

Le code ci-dessus n'appelle pas de commentaire particulier, si ce n'est la création de l'objet de type Hello contenant une référence vers le serveur HelloServer. On appelle en effet la méthode lookup() de la classe Naming qui a pour but de renvoyer la référence du service dont on précise l'URL en argument. Cette URL est construite grâce aux méthodes getCodeBase() et getHost() qui permettent de récupérer le nom de la machine sur laquelle s'exécute le serveur. On appelle ensuite la méthode distante comme s'il s'agissait d'une méthode normale qui renvoie une chaîne de caractères.

Cette chaîne est affichée dans la zone dédiée à l'applet via la méthode paint() que nous avons présentée dans le chapitre précédent.

 

Création de la page HTML appelant l'applet

<HTML>

<title>Hello World</title>

<center> <h1>Hello World</h1> </center>

Le message envoyé par HelloServer est:

<p>

<applet codebase="../.."

code="rmidemo.hello.HelloApplet"

width=500 height=120>

</applet>

</HTML>

A noter que l'on spécifie l'endroit où se trouve l'applet en utilisant un adressage relatif au répertoire où se trouve ce fichier HTML et que le nom de l'applet est indiqué en précisant le nom du package auquel elle appartient.

Nous avons terminé l'écriture des différents fichiers impliqués dans le mécanisme. Il faut maintenant :

  1. compiler tous les programmes Java,
  2. créer les fichiers squelette et stub, qui assurent la communication entre le serveur et le client,
  3. déployer l'application,
  4. démarrer le registry, le serveur et l'applet.

 

Compilation des sources Java

Dans toute la suite, on considérera que le répertoire de destination de l'application distribuée est le répertoire public_html/codebase situé dans le home directory de l'utilisateur (typiquement sous UNIX). On exécute le compilateur comme suit :

javac -d $HOME/public_html/codebase Hello.java HelloImpl.java

HelloApplet.java

à partir du répertoire où se trouvent les fichiers sources que nous avons présentés dans ce paragraphe.

 

Génération du squelette et du stub

Cette fois ci, on utilise le compilateur rmic comme suit :

rmic -d $HOME/public_html/codebase rmidemo.hello.HelloImpl

qui va générer deux fichiers .class utilisés lors de l'exécution de l'application.

 

Déploiement de l'application

Puisque nous avons déjà compilé les différents fichiers en précisant le répertoire de déploiement que nous avons choisi, il ne reste plus qu'à copier le fichier HTML (index.html) dans le sous répertoire rmidemo/hello du répertoire de déploiement indiqué plus haut.

 

Démarrer le registry, le serveur et l'applet

Nous arrivons enfin à la dernière étape. On démarre tout d'abord le registry sur le serveur en tapant :

start rmiregisty sous Windows ou rmiregistry & sous UNIX.

C'est ce programme qui va gérer la base d'équivalence entre les noms de services et les objets associés.

On démarre ensuite le serveur comme suit :

java -Djava.rmi.server.codebase=http://localhost/~username/codebase/

rmidemo.hello.HelloImpl &

Le serveur doit indiquer qu'il a enregistré son service auprès du registry. A noter que l'on spécifie la valeur de la ressource codebase en argument de l'interpréteur java.

Enfin, on lance l'applet avec l'appletviewer comme par exemple :

appletviewer http://localhost/~username/codebase/rmidemo/hello/index.html

Si tout s'est déroulé correctement, on doit voir apparaître à l'écran une applet affichant le texte "Hello World!".

Nous allons maintenant présenter rapidement les Javabeans avant de voir JDBC plus en détail.

 

2. Les javabeans

Dans ce paragraphe nous ne présenterons les Javabeans que dans un cadre général, sans donner d'exemple de code.

Un Java Bean est un composant réutilisable, écrit en Java, qui peut être manipulé avec un logiciel de construction graphique. On peut ainsi construire une application complète rien qu'en assemblant entre eux des Java Beans. Nous verrons plus loin que ce mécanisme est comparable aux contrôles ActiveX que nous présenterons dans la deuxième partie de ce document.

Un bean peut être un simple objet graphique comme un bouton mais pas seulement, car un assemblage de beans devient lui-même un bean. Un mécanisme dit d'introspection permet à un outil de développement d'explorer un bean afin d'en extraire ses propriétés et son comportement (signature des méthodes, classes, événements générés etc.).

Ces outils de développement dédiés permettent donc d'assembler des beans mais également de les faire interagir entre eux, de façon à pouvoir construire une application interactive sans qu'il ne soit nécessaire d'entrer la moindre ligne de code. On associe ainsi un événement généré par un bean à un traitement effectué par un autre, et ce, rien qu'en manipulant des objets graphiques dans une fenêtre.

Il est également important de noter que les caractéristiques et le comportement d'un bean ne sont pas figés. L'utilisateur peut en effet les modifier avant de l'intégrer dans une application, ce qui rend ces composants réellement réutilisables.

Précisons que Sun distribue un kit de développement spécifique, le BDK (Beans Development Kit) permettant de créer des beans.

 

3. JDBC

Java DataBase Connectivity (JDBC) est une API qui permet d'accéder à une base de données, directement en Java. JDBC a été conçu, comme Java, pour être indépendant de la plate-forme sur laquelle il est utilisé, c'est pourquoi il est basé sur SQL (Structured Query Language). JDBC utilise un driver manager qui est en fait spécifique à chaque base de données à laquelle on peut se connecter.

Une base de données est identifiée par une URL particulière qui contient trois champs :

  1. un champ protocole ayant pour valeur "jdbc",
  2. un sous-protocole, contenant le nom du driver utilisé. Le premier driver disponible est "odbc" (via un pont jdbc-odbc), vu que JDBC s'en est inspiré.
  3. un identifiant de base de données.

Exemple d'URL :

jdbc:odbc:mabase

Le package contenant les différentes classes exploitées par JDBC est java.sql.

On distingue trois étapes permettant de travailler sur une base de données :

  1. Etablissement de la connexion avec la base de données.
  2. Envoi de la requête SQL à cette base.
  3. Traitement du résultat de cette requête.

L'établissement de la connexion s'effectue en appelant la méthode getConnection() de la classe DriverManager, comme suit :

Connection con = DriverManager.getConnection (

"jdbc:odbc:mabase", "login", "mot de passe");

Cette méthode renvoie un objet Connection qui sera exploité pour l'envoi de la requête. On spécifie dans l'appel de getConnection() l'URL de la base, un nom d'utilisateur et un mot de passe. Une exception est levée en cas d'impossibilité d'accès à la base.

Ensuite, on prépare la requête SQL à exécuter sur la base. Pour ce faire, on crée tout d'abord un objet Statement, grâce à la méthode createStatement() de la classe Connection :

Statement stmt = con.createStatement();

puis on envoie la requête SQL à la base via la méthode executeQuery() :

ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");

Cette méthode renvoie le résultat de cette requête dans une table de type ResultSet.

Enfin, on traite les informations ainsi reçues en parcourant la table ligne par ligne, comme par exemple :

while (rs.next()) {

int x = getInt("a");

String s = getString("b");

float f = getFloat("c");

}

Dans cet exemple, on parcourt la table tant qu'elle contient encore des données. La méthode next() renvoie true tant que c'est le cas. Les méthodes getXXX permettent quant à elles de récupérer les données de cette table dans des variables afin de pouvoir les utiliser dans le programme.

 

Nous avons terminé cette première partie consacrée à Java et nous allons maintenant aborder la technologie ActiveX de Microsoft.