Plan du polycopié

Cette deuxième partie du cours POD suppose de connaître le noyau objet du langage Java comme présenté dans le cours de tronc commun de maîtrise interfaces et architectures d'applicatons. Les ouvrages indiqués dans la bibliographie permettent une mise à niveau, en particulier l'ouvrage ``Java : de l'esprit à la méthode'' est une introduction au langage Java.
La première section introduit les processus légers de Java qui implante les moniteurs et a une gestion des priorités entre threads ainsi que la notion de groupe de threads.
La deuxième section décrit rapidement la bibliothèque AWT pour la construction d'interface graphique. Elle montre les difficultés de communication entre les objets source d'événements ``utilisateurs'' et les objets traitants ces actions. C'est l'occasion d'introduire les classes locales qui facilitent cette communication.
La troisième section montre qu'une applet n'est en fait qu'un composant graphique de base (Panel) qui implante l'interface des processus légers (Runnable).
La quatrième section revient sur les prises de communication Internet (socket) en décrivant les principales classes Java pour leur mise en oeuvre.
La cinquième section présente le modèle de programmation parallèle à mémoire distante (distincte ou partagée) et introduit les objets répartis. Ceux-ci sont mis en oeuvre via la bibliothèque RMI (Remote Method Invocation) qui autorise l'envoi d'un message à un objet distant.
La sixième section s'intéresse à la connaissance des objets dans un programme. Cette introspection va permettre d'inspecter des classes et des objets dont onne connait pas le source.
La septième section montre le mécanisme de chargement dynamique de classes.


La dernière section effectue une comparaison des langages Objective Caml et Java, tant au niveau du noyau objet et de son typage, qu'au niveau des bibliothèques et des outils et environnement de développement.


Planche : 1


Bibliographie

Planche : 2


Processus légers (Thread)

Modèle de parallélisme à mémoire partagée Un programme exécutant plusieurs threads attendra que toutes soient finies avant de se terminer.

Un thread s'exécute jusqu'au moment où Il y aura une différence de comportement selon l'implantation de la JVM sur les systèmes gérant le temps partagé et les autres. Dans le premier cas le multi-threading sera préemptif alors que dans le second cas il sera coopératif. Pour écrire un code portable sur différents systèmes, il est préférable de ne pas faire d'hypothèse sur l'implantation des threads.

Les threads JAVA implantent un mécanisme de ``moniteurs'' protégeant l'accès aux objets ``synchronisés''. L'exclusion mutuelle s'effectue sur l'appel des méthodes déclarées synchronized dans la définition de la classe de l'objet receveur. Une seule exécution d'une méthode synchronized peut être effectuée en même temps, les autres appels sont alors bloqués. Il existe deux méthodes primitives principales wait et notify qui permettent une communication entre threads ayant accès au même moniteur.


Planche : 3


Création et Exécution

2 possibilités : Quand start() retourne, la tâche termine.

class TestThreads {
  public static void main (String []args) {
    MyThread mt1 = new MyThread(1);
    MyThread mt2 = new MyThread(2);
    mt1.setPriority(10);
    mt2.setPriority(10);
    mt1.start();
    mt2.start();
  }
}
class MyThread extends Thread {
  int num;
  int st = 1;
  MyThread(int n) { num = n;} 

  public void run() {
    int i=0;
    while (st < 5000001) {
      if ((st % 1000000) == 0) {System.out.println("MT" + num+ " trace = "+st);}
      st++;
      yield();
    }
  }   
}

Planche : 4


Méthodes sur les threads
Un thread une fois crée peut être dans les états suivants : La méthode start() rend un thread ``exécutable'', et lance la méthode run(). Le thread se termine quand cette méthode a fini son exécution.
La méthode stop() arrête un thread (dépréciée).
Les méthodes suspend() (dépréciée), sleep(i) et wait met un thread dans l'état bloqué.
Les méthodes resume() (dépréciée) et notify() fait passer un thread de l'état ``bloqué'' à l'état ``exécutable''.
La méthode yield() fait passer de l'état ``en cours d'exécution'' à l'état exécutable.
La méthode join() attend la fin d'un thread.
La méthode setPriority(i) modifie la priorité d'un thread (valeur entre MIN_PRIORITY et MAX_PRIORITY.



Planche : 5


Relations entre threads
  1. sans relation
  2. avec relation mais sans synchronisation
  3. relation d'exclusion mutuelle
  4. relation d'exclusion mutuelle avec communication
Exemple de deux threads sans relation
public class SR {
  public static void main ( String []s) {
    Thread t1 = new Aff("Bonjour");
    t1.start();
    new Aff("Au revoir").start();
  }
}

class Aff extends Thread {
  String txt;
  Aff (String s){txt=s;}

  public void run () {
    while(true) {
      ES.writeln(txt);
      yield();
    }
  }
}

Planche : 6


Relation sans synchronisation

sur une même structure de données à des endroits différents :
Exemple : calcul de la nouvelle génération du jeu de la vie
par différentes threads.
ou ouverture d'une connexion sur une socket


Planche : 7


Synchronisation

Verrou (accès exclusif) sur un objet :
  synchronized (o) { ... }
  }

// ou sur une methode

synchronized type nom_methode( ..) { ...}
Une seule thread peut prendre le verrou (les autres attendent).
Le verrou est rendu à la sortie du bloc de synchronisation.


Planche : 8


Relation d'exclusion mutuelle : moniteurs

Dès que le verrou est pris :
  1. appel d'une méthode synchronized
    il garantit qu'une seule méthode ``synchronisée'' peut être appelée sur cet objet
  2. bloc synchronized sur un objet
    il verouille l'objet.
Un moniteur est associé à une donnée et aux fonctions qui verrouillent l'accès à cette donnée. En Java, cette donnée sera un objet et les fonctions les méthodes déclarées synchronized dans la définition de la classe de cet objet.
Il y a création d'un moniteur pour chaque objet qui possède au moins une méthode synchronized.
Quand un thread a pris le moniteur sur un objet (c'est-à-dire est entré dans l'exécution d'une méthode ``synchronisée''), les autres threads voulant exécuter une méthode ``synchronisée'' sur cet objet sont bloqués. Quand le premier thread a terminé l'exécution du code de la méthode ``synchronisée'' elle libère le moniteur qui peut alors être pris par un autre thread.
Les moniteurs permettent aussi la communication entre threads par le mécanisme d'attente/notification (wait et notify).


Planche : 9


Communication
à l'intérieur d'un bloc de synchronisation

Planche : 10


Exclusion mutuelle avec communication
Producteur/Consommateur :

// thread du producteur       // thread consommateur
entree code sync                entree code sync
  while(buffer plein)           while (buffer vide)
    wait()                        wait()
  produit_1()                   consomme_1()
  notify()                      notify()
Exemple : un producteur/consommateur
public class Sync {
  public static void main (String [] args) {
     Producteur p = new Producteur();
     p. start();
     Consommateur c = new Consommateur(p);
     c.start();
  }
}

class Consommateur extends Thread {
  Producteur mien;
  Consommateur(Producteur un){mien=un;}

  public void run() {
    while(true) {
       String r = mien.consomme();
       System.out.println("Consommation : "+r);
       try {sleep((int)(1000*Math.random()));}
       catch (Exception e0) {}
   }
 }
}

class Producteur extends Thread {
  private String [] buffer = new String [8];
  private int ip = 0;
  private  int ic = 0;

  public void run() { 
    while(true) {
      produce();
    }
  }

synchronized void produce() { 
  while (ip-ic+1 > buffer.length) {try{wait();} catch(Exception e) {} }
  buffer[ip % 8] = "Machine "+ip;
  System.out.println("produit : ["+(ip % 8)+"]  "+buffer[ip % 8]);
  ip++;
  notify();
}

synchronized String consomme() { 
   while (ip == ic) {try{wait();} catch(Exception e) {}}
   notify();
   return buffer[ic++%8];
}
}

Planche : 11


Abstract Windowing Toolkit

Paquetage de classes pour la construction d'interfaces graphiques :


Planche : 12


Hiérarchie de classes


Planche : 13


Composants

objet abstrait ayant : une position, une taille, dessinable et pouvant recevoir des évènements

possèdent un contexte graphique (Graphics).


Planche : 14


Conteneurs

Pouvant suivre différents algorithmes de placement de composants. Application graphique : instancie au moins une fois une Frame
Applet : est un composant graphique

Exemple de dessin
import java.awt.*;
import java.awt.event.*;

class TestXframeD {
  static public void main(String [] args) {
    XframeD d = new XframeD();
  }
}


class XframeD extends Frame { 

   Canvas p;
   Color c1 = Color.blue;
   Color c2 = Color.red;
   Color c=c1;

 XframeD() {
   super("DESSIN");

   p = new Canvas();
   p.setSize(400,300);
   
   this.setSize(420,320);
   this.add(p);
   this.pack();
   this.show();
 }

  public void paint( Graphics gs) { 
   Graphics g = p.getGraphics();
   if (c.equals(c1)){c=c2;}
   else {c=c1;}
   g.setColor(c);
   g.drawString("Mon dessin",160,40);
   
   for (int i = 0; i< 10; i++) {
     g.fillRect(40+i*20,100,10,10);   
   }
  } 
}
Créer une interface
import java.awt.*;
import java.awt.event.*;

class TestXframe {
  static public void main(String [] args) {
    Xframe e = new Xframe();
    e.init_event();
  }
}

class Xframe extends Frame { 

   String monlogin  ="emmanuel";
   String monpasswd ="pilpoil";
   TextField login;
   TextField passwd;
   Panel p;


 Xframe() {
   super("LOGIN");
   p = new Panel();
   p.setSize(400,500);
   login = new TextField(8);
   passwd = new TextField(8);
   p.add(new Label("Login : "));
   p.add(login);
   p.add(new Label("Password : "));
   passwd.setEchoChar('*');
   p.add(passwd);
   this.add(p);
   this.pack();
   this.show();
 }

  void init_event() { 
  }
} 



Planche : 15


Evénements

En JDK 1.1 : modèle par délégation.


Planche : 16


Sources et délégués
Les composants (Source) peuvent déclencher des évènements
Ceux-ci seront traités par les ``délégués'' enregistrés:
instances de classe implantant des interfaces `Listener
public <TypeEvt>Listener set<TypeEvt>Listener(
                                 <TypeEvt>Listener monDelegue)
public <TypeEvt>Listener add<TypeEvt>Listener(
                                 <TypeEvt>Listener monDelegue)
public <TypeEvt>Listener remove<TypeEvt>Listener(
                                 <TypeEvt>Listener monDelegue)
<TypeEvt> est remplacé par un événement :
addMouseListener(MouseListener monDelegue)


Planche : 17


Evénements et Composants

Chaque composant peut déclencher certains événements :
un Panel pourra déclencher un événement souris (MouseEvent).
Un délégué implante une interface Listener.
Il existe des adaptateur implantant certaines interfaces (méthodes > 1).
Listener Interface Adaptater Class Methods
ActionListener NON actionPerformed
MouseListener MouseAdapter mousePressed ...

Exemple de délégué
import java.awt.*;
import java.awt.event.*;

class AdaptateurAction implements ActionListener {

  XframeE A;

  AdaptateurAction(XframeE a) {
    A=a;
  }

  public void actionPerformed(ActionEvent e) {
    if ((e.getSource() == A.login) || (e.getSource() == A.passwd))
    { if ((A.login.getText().equals(A.monlogin)) &&
         (A.passwd.getText().equals(A.monpasswd)))
      {A.OK=true; A.good();}
      else {A.nogood();}
    }
  }
}

Exemple d'association de traitement d'événements
import java.awt.*;
import java.awt.event.*;

class TestXframeE {
  static public void main(String [] args) {
    XframeE e = new XframeE();
    e.init_event();
  }
}

class XframeE extends Xframe {

  boolean OK = false;

  void init_event () { 
    AdaptateurAction aa;
    aa = new AdaptateurAction(this);
    login.addActionListener(aa);
    passwd.addActionListener(aa);    
  }

  void good() {
    ES.writeln("C'est parti");
    System.exit(0);
  }

  void nogood() {
    ES.writeln("Essaie encore!!!");
  }
  
}
Cet exemple utilise les classes définies pages ?? et ??.


Planche : 18


Classes locales
Pour simplifier la communication entre le délégué et le composant :
le JDK 1.1 a introduit les classes locales ou internes (y compris anonymes).
Þ cela permet de ne pas passer le composant comme variable du délégué.
Les variables dínstance du composant sont directement accessibles.


Planche : 19


Déclaration de classes locales

  1. nouveau membre (champs) d'une classe
  2. à l'intérieur d'un bloc d'instructions
Premier cas : accessible par la notation ``point''
Deuxième cas : non accessible en dehors du bloc
mais la valeur peut sortie du bloc (si implantation d'une interface)

Exemple d'une classe locale
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

class Test3_1 extends Applet {

  class AdaptateurSouris extends MouseAdapter 
    implements MouseListener {
      public void mousePressed (MouseEvent e)
      {
        int x = e.getX();
        int y = e.getY();
        System.out.println("x = " + x + "  y = "+y);
        getGraphics().drawString("salut tout le monde",x,y);
      }
     }

  Test3_1() {
    MouseListener clic = new AdaptateurSouris();

    addMouseListener(clic);
  }

  public void init () {
    this.setBackground(Color.white);
  }
}

Le constructeur Test3_1() crée une instance de la classe locale AdaptateurSouris qui est immédiatement ajoutée comme traiteur des événements souris à l'applet Test3_1() construite.


Planche : 20


Classe (locale) anonyme

Dans les cas où le nom de la classe n'a pas d'importance :
elle peut être déclarée anonymement!!!

Exemple d'une classe locale anonyme
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

class Test3_2 extends Applet {
  Test3_2() {
    MouseListener clic = new MouseAdapter () {
      public void mousePressed (MouseEvent e)
      {
        int x = e.getX();
        int y = e.getY();
        System.out.println("x = " + x + "  y = "+y);
        getGraphics().drawString("salut tout le monde",x,y);
      }
     };
    addMouseListener(clic);
  }

  public void init () {
    this.setBackground(Color.white);
  }
}

Planche : 21


Applets

La classe Applet hérite de Panel et implante Runnable.

Une applet possède une zone graphique (conteneur Panel) qui n'ouvre pas une nouvelle fenêtre.


Planche : 22


cycle de vie
init()Þstart()Þstop()Þdestroy() où : void paint(Graphics g) : sera appelée à chaque réaffichage.


Planche : 23


Exécution


Planche : 24


Balise
<html>
 <head> Exercices en Java
 </head>
<body>
 <H1> Test </H1>
  <P>
  <applet code="Test1" height=400 width=400>
  <P><EM> Not a java-powered browser! </EM>
  </applet>
</body>
</html>
Applet de dessin
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class graf extends Applet { 
 
 
  public void paint(Graphics g) {
    g.drawRect(25,30,60,40);
    g.drawRect(125,30,100,100);

    g.setColor(Color.cyan);
    g.drawOval(25,30,60,40);
    g.drawOval(125,30,100,100); 
  } 

} 

Applet et applications
Il peut être utile de créer une application qui lance une applet. Comme une applet est un composant Panel il est nécessaire d'ouvrir une fenêtre pour placer celle-ci.
import java.awt.*;

public class grafa {
  public static void main(String []args) {
    Frame d = new Frame();
    d.setSize(400,300);
    graf g = new graf(); 
    g.setSize(300,200);
    d.add(g);
    d.show();
    g.init();
    g.start();
    d.paint(d.getGraphics());
  }
}
Applet de login
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class passwdTest extends Applet {


String monlogin  ="tartempi";
String monpasswd ="itaparit";
TextField login;
TextField passwd;
boolean OK = false;


ActionListener RC = new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    if ((e.getSource() == login) || (e.getSource() == passwd))
    { if ((login.getText().equals(monlogin)) &&
         (passwd.getText().equals(monpasswd)))
      {OK=true; good();}
      else {nogood();}
    }
  }
};

  public void init() {
    login = new TextField(8);
    passwd = new TextField(8);
    add(new Label("Login : "));
    add(login);
    add(new Label("Password : "));
    passwd.setEchoChar('*');
    add(passwd);
    login.addActionListener(RC);
    passwd.addActionListener(RC);
  }

  public void good() {
    resize(120,180);
    this.getGraphics().drawString("c'est parti...",10,150);
  }

  public void nogood() {
    this.getGraphics().drawString("identification incorrecte",10,100);
  }
}

Planche : 25


Persistance

conservation d'un objet en dehors de l'exécution courante :
dans un fichier ou un flux

But:  récupération ultérieure dans le même programme ou un autre.

Difficultés:  : En jdk1.1 : mécanisme de sérialisation!!!


Planche : 26


Sérialisation


Planche : 27


Que stocke-t-on?
Le stockage contient : La clé permet de vérifier la version de la classe.
Le modifieur transient permet d'indiquer qu'un champs ne doit pas être sérialisé.
possibilité de changer le mécanisme de sérialisaiton en implantant une classe Externalizable respectant les interfaces writeExternal et readExternal.

La méthode void writeObject(Object) écrit dans un flux (de type ObjectOutputStream) une instance de classe qui implante l'interface Serializable.
La méthode Object readObject() lit dans un flux (de type ObjectInputStream) un persistant et retourne une valeur de type Object. Il est alors utile de faire une coercition de type (vers une sous-classe d'Object) sur la valeur lue pour pouvoir la manipuler avec son bon type. Pour que cette contrainte de type puisse avoir lieu, il est nécessaire que la classe soit connue du programme (ou chargeable à ce moment là). Si la classe n'existe pas une exception est alors déclenchée de type ClassNotFound.
Comme la classe doit être connue par le programme qui récupère le persistant, il n'est pas nécessaire de conserver dans celui-ci le code des méthodes. Seuls les champs de données ``sérialisés'' seront conservées dans le persistant.

Exemple
import java.io.*;
class Exemple20 implements Serializable   {
  String nom;
  Exemple20 autres;

  Exemple20() {nom=null;autres=null;}
  Exemple20(String n, Exemple20 e) {nom=n;autres=e;}

  boolean estVide() {return (nom == null);}
  void detruit() {nom=null;autres=null;}
  public String toString() { 
    if (estVide()) return "[]";
    else if (autres.estVide()) return nom;
    else return nom+"::"+autres.toString();
  }
  public void ajoute (String unNom) {
    System.out.println("*");
    Exemple20 e = new Exemple20(nom,autres);
    autres=e;
    nom=unNom;
  }
}

class Execute  {
  public static void main (String[] args) {
    Exemple20 e = new Exemple20();
    ObjectOutputStream out;
    ObjectInputStream in;
        
   try {
    e.ajoute("machin");
    e.ajoute("truc");
    System.out.println("1 : "+e);
    out = new ObjectOutputStream(new FileOutputStream("Exemple20.dat"));
    out.writeObject(e);
    out.flush(); out.close();
    e.detruit();
    System.out.println("2 : "+e);
    in = new ObjectInputStream(new FileInputStream("Exemple20.dat"));
    e.ajoute("bidule");
    e.autres = (Exemple20)in.readObject();
    in.close();
    System.out.println("3 : "+e);
    }
    catch (java.lang.ClassNotFoundException exc){System.err.println(exc);}
    catch (StreamCorruptedException exc) {System.err.println(exc);}
    catch (IOException  exc) {System.err.println(exc);}
  }
}


Planche : 28


Exécution
java Execute

*
*
1 : truc::machin
2 : []
*
3 : bidule::truc::machin
* : trace de ajoute(..)

La classe Exemple20 est une classe pour les listes de chaînes de caractères. La représentation de la liste vide est une instance de cette classe dont les variables d'instance nom et autres valent null. Cette classe implante l'interface Serializable. Cette interface ne demande pas l'écriture de méthodes particulières.
Les * indique l'ajout d'un élément à la liste. A la trace 1 il y a deux noms dans la liste. Celle-ci est sauvée dans le fichier Exemple20.dat puis détruite dans l'exécution du programme (trace 2). Une nouvelle tête de liste est créée. Le persistant stocké dans le fichier Exemple20.dat est ensuite relu et son type est forcé en Exemple20 et ajouté en queue de liste. Le résultat est affiché à la trace 3.
Il est à noter que la contrainte de type effectue en fait un test dynamique de type sur la valeur qui vient d'être lue. Si la classe indiquée n'existe pas une exception ClassNotFound est alors déclenchée.


Planche : 29


Sockets

paquetage : java.net : fournit un ensemble de classes pour communiquer sur le réseau Internet. La classe InetAddress représente les adresses Internet. Elle est utilisée dans la classe Socket.


Planche : 30


Classes de haut niveau

Planche : 31


Exemple de client/serveur

Conversion de chaînes en MAJUSCULE via un serveur (Java : de l'esprit à la méthode)
Le serveur est construit à partir de 2 classes : Lors d'une connexion d'un client au serveur (Serveur.run()) une nouvelle instance de Connexion est créée.

Partie serveur générique
Cet exemple provient de l'ouvrage ``Java : de l'esprit à la méthode''.

import java.io.*;
import java.net.*;

public class Serveur extends Thread {
  protected static final int PORT =45678;
  protected ServerSocket ecoute;


Serveur ()
{ try
    { ecoute = new ServerSocket(PORT);}
  catch (IOException e)
    {System.err.println(e.getMessage());
     System.exit(1);
    }
  System.out.println(
     "Serveur en ecoute sur le port : "+PORT);
  this.start();
}

public void run ()
{ try
    {while (true)
       {Socket client=ecoute.accept();
        Connexion c = new Connexion (client);}}
  catch (IOException e)
    {System.err.println(e.getMessage());
     System.exit(1);}
}

public static void main (String[] args)
  {new Serveur();}

}
Partie serveur spécifique
class Connexion extends Thread {
  protected Socket client;
  protected DataInputStream in;
  protected PrintStream out;

  public Connexion(Socket client_soc)
  { client=client_soc;
    try
     { in=new DataInputStream(client.getInputStream());
       out=new PrintStream(client.getOutputStream());}
    catch (IOException e)
     {try {client.close();}
      catch (IOException e1){}
      System.err.println(e.getMessage());
      return;}
    this.start();
  }

  public void run()
  { try
      { while (true)
        {String ligne=in.readLine();
         if (ligne.toUpperCase().compareTo("FIN")==0) break;
         System.out.println(ligne.toUpperCase());
         out.println(ligne.toUpperCase());
         out.flush();
        }}
     catch (IOException e)
      {System.out.println("connexion : "+e.toString());}
     finally
      {try {client.close();}  catch (IOException e) {}}

  }

}
Partie client
import java.io.*;
import java.net.*;

public class Client {
  protected static final int PORT=45678;

  public static void main(String[] args)
  {
    Socket s=null;
    if (args.length != 1)
      {System.err.println("Usage: java Client <hote>");
       System.exit(1);}
    try
      { s=new Socket (args[0],PORT);
        DataInputStream canalLecture = 
          new DataInputStream(s.getInputStream());
        DataInputStream console = 
          new DataInputStream (s.getInputStream());
        PrintStream canalEcriture = 
          new PrintStream(s.getOutputStream());
        System.out.println("Connexion etablie : "+
                           s.getInetAddress()+" port : "+s.getPort());
       String ligne = new String();
       char c;
       while (true)
         { System.out.print("?"); System.out.flush();
           ligne = "";
           while ((c=(char)System.in.read()) != '\n') {ligne=ligne+c;}
           canalEcriture.println(ligne);
           canalEcriture.flush();
           ligne=canalLecture.readLine();
           if (ligne == null)
             {System.out.println("Connexion terminee"); break;}
           System.out.println("!"+ligne);
         }
      }
    catch (IOException e) {System.err.println(e);}
    finally
     { try {if (s != null) s.close();}
       catch (IOException e2) {}
     }
  }
}

Planche : 32


Execution
[chaillou@ippc56]$ java Serveur
Serveur en ecoute sur le port : 45678
ADIEU
MONDE CRUEL

coté client
[chaillou@ippc56]$ java Client ippc56
Connexion etablie : ippc56/134.157.116.56 port : 45678
?adieu
!ADIEU
?monde cruel
!MONDE CRUEL
?fin
Connexion terminee

Planche : 33


Communication avancée

Pour établir un nouveau service, il est souvent nécessaire de définir un ``protocole'' de communication entre le serveur et les clients.

...


Planche : 34


Objets distribués

Possibilités: 

clients/serveurs + persistance Þ transport d'objets par copie (et création de nouvelles instances)
Références distantes:  références de plusieurs endroits du réseau au même objet
pour invoquer ses métodes et/ou modifier ses variables d'instances.


Planche : 35


Difficultés de mise en oeuvre
le jdk 1.1 offre un mécanisme simple nommé RMI (Remote Method Invokation) permettant de manipuler des objets distants.


Planche : 36


RMI


Planche : 37


Structure générale
                 client              serveur
                   ^                   ^
                   |                   |
                   |                   |
                   |                   |
                   v                   v
            ---------------     ---------------
            |   stub      |     |   skeleton  |
            -----------------------------------
            |      couche des references      |
            -----------------------------------
            |      couche transport           |
            -----------------------------------

Planche : 38


Serveur
Le serveur s'en trouve compliqué sur les services suivants :

Planche : 39


Paquetages
Les paquetages utiles sont : L'interface d'objets distants étend l'interface Remote en ajoutant les méthodes désirées qui peuvent déclencher des RemoteException.


Planche : 40


Exemple : Points distants

Interface: 
import java.rmi.*;
public interface PointRMI extends Remote {

  void moveto (int a, int b)    throws RemoteException;

  void rmoveto (int dx, int dy) throws RemoteException;

  void affiche()    throws RemoteException;

  double distance() throws RemoteException;
}

Planche : 41


Implantation d'interfaces distantes
Implantation de l'interface: 
  import java.rmi.*;
  import java.rmi.server.UnicastRemoteObject;

public class PointD extends UnicastRemoteObject
                    implements PointRMI {

  int x,y;
  PointD(int a, int b) throws RemoteException {x=a;y=b;}
  PointD() throws RemoteException {x=0;y=0;}

  public void moveto (int a, int b)
    throws RemoteException
  { x=a; y=b;}

  public void rmoveto (int dx, int dy)
    throws RemoteException
  { x = x + dx; y = y + dy;}

  public void affiche()
   throws RemoteException
  { System.out.println("(" + x + "," + y + ")");}

  public double distance()
    throws RemoteException
  { return Math.sqrt(x*x+y*y);}
}

Planche : 42


Compilation
rmic PointD qui créera les fichiers PointD_Stub.class et PointD_Skel.class.


Planche : 43


création et enregistrements
Le serveur de points lui va créer des instances de PointD et les enregistrer (rebind) auprès du démon (rmiregistery) qui gère le protocole rmi.

  import java.rmi.*;
  import java.rmi.server.UnicastRemoteObject;

  public class Creation {

    public static void main (String args[]) {

      System.setSecurityManager(new RMISecurityManager());
      try {
        PointD p0 = new PointD();
        PointD p1 = new PointD(3,4);

        Naming.rebind("//youpou.lip6.fr/point0",p0);
        Naming.rebind("//youpou.lip6.fr/point1",p1);
        System.out.println("Objets distribues 'p0' " +
            "et 'p1' sont enregistres");
      }
      catch (Exception e) { e.printStackTrace();
      }
    }
  }

Planche : 44


Commentaires
La classe Naming permet de nommer des objets pour leur enregistrement sur le serveur (bind, rebind, unbind, list, lookup).
Compilation classique avec javac.


Planche : 45


un client

import java.rmi.*;
public class Client {
  public static void main( String argv[]) {
    String url0="rmi://youpou.lip6.fr/point0";
    String url1="rmi://youpou.lip6.fr/point1";
 try {
   PointRMI p0 = (PointRMI)Naming.lookup(url0);
   PointRMI p1 = (PointRMI)Naming.lookup(url1);
   p0.affiche(); p1.affiche();
   p0.rmoveto(7,12);
   p1.rmoveto(5,6);
   p0.affiche();    p1.affiche();
   if (p0.distance() == p1.distance())
     System.out.println("c'est le hasard");
   else
       System.out.println("on pouvait parier");
   }
 catch (Exception e) {
     System.err.println("exception : " +
                         e.getMessage());
     e.printStackTrace();
}  }  }

Planche : 46


Exécution
1ère exécution: 
(0,0)
(3,4)
(7,12)
(8,10)
on pouvait parier
2ème exécution: 
(7,12)
(8,10)
(14,24)
(13,16)
on pouvait parier

Planche : 47


Exceptions
Si le démon ne fonctionne plus au moment de l'utilisation d'un objet distant, on récupère l'exception dans le catch et on affiche les messages suivants :
exception : Connection refused to host;
...
et si l'objet demandé n'est pas enregistré, la suivante :
exception : point0
java.rmi.NotBoundException: point0

Planche : 48


port de communication
Par défaut, le port du service rmi est le 1099. Il est possible de changer de numéro. Pour cela le serveur d'enregistrement doit être lancé avec le paramètre du port :
rmiregistry 2000&
et il faut indiquer ce nouveau port aux URL employées :
//youpou.lip6.fr:2000

Planche : 49


CORBA

Common Object Request Broker Architecture : norme (OMG - 1990)
permettant : Pour être indépendant CORBA définit le langage IDL.


Planche : 50


IDL
Langage de définition d'interfaces qui permet de décrire les méthodes et champs visibles des objets.
Accepte l'héritage simple et multiple d'interfaces. Point: 
interface Point {
  void moveto(in int a, in int b);
  void rmoveto(in int dx, in int dy);
  void affiche();
  double distance();
}
A partir de cette description, le compilateur IDL (fourni avec un ORB), engendreles stubs et les skeletons pour le serveur et cela pour différents langages (Smalltalk, C++, Java ...)


Planche : 51


Introspection

Objectifs:  : explorer et de modifier des valeurs (des objets en Java) existant pendant une exécution.

Pour cela une classe elle-même est considérée comme un objet, ce qui permet : mais ne permet pas de définir une nouvelle classe ni ne modifier la hiérarchie des classes existante,

Il existe un véritable besoin de pouvoir explorer dynamiquement (sans connaître les sources de la compilation) l'intérieur des objets et des classes Java.

Cela permet de construire (facilement) des extensions au langage et de pouvoir construire des environnements visuels (ne faisant qu'explorer de l'extérieur) comme par exemple pour les Java Beans.


Planche : 52


L'API java.lang.reflect permet, si les conditions de sécurité l'acceptent de :

les domaines d'applications sont :

Planche : 53


classes pour l'introspection

Planche : 54


class Class

méthodes principales: 

La classe Class représente une classe Java. On construit un objet Class soit en envoyant le message getClass() sur une instance, soit en la chargeant dynamiquement avec la méthode Class.forName auquel on passe le nom de la méthode sous forme de chaîne de caractères.

On peut à partir d'une classe créer une nouvelle instance de celle-ci : la méthode newInstance() crée une nouvelle instance de la classe qui reçoit ce message en utilisant son constructeur sans paramètre.

D'autre part il est possible de récupérer les informations d'une classe (champs, méthodes, constructeurs, ...). On obtient alors des instances des classes : Field, Method, Constructor sous forme de tableaux. L'exemple suivant montre comment afficher ces informations.

Exemple d'exploration
Ce programme liste tous les attributs et méthodes publics d'une classe passée en paramètre :

import java.lang.reflect.*;

public class Lecture {
  public static void main(String args[]) {
    Class c = null;
    Field[] champs = null;
    Method[] methodes = null;

    try {
      c = Class.forName(args[0]);
      champs = c.getDeclaredFields();
      methodes = c.getMethods();
    }
    catch (ClassNotFoundException e) { // ...;
                                       System.exit(0);}
    catch (SecurityException e) { // PB d'autorisation
                                        System.exit(0);}
    
    for (int i=0; i< champs.length;i++) {
      Field uc = champs[i];
      System.out.println("champs "+i+" : "+uc);
    }

    for (int i = 0; i < methodes.length; i++) {
      Method um = methodes[i];
      System.out.println("methodes "+i+ " : " + um);
    }
  }

}
Son exécution donne :

$ java Lecture java.lang.Boolean

champs 0 : public static final java.lang.Boolean java.lang.Boolean.TRUE
champs 1 : public static final java.lang.Boolean java.lang.Boolean.FALSE
champs 2 : public static final java.lang.Class java.lang.Boolean.TYPE
champs 3 : private boolean java.lang.Boolean.value
champs 4 : private static final long java.lang.Boolean.serialVersionUID
methodes 0 : public static java.lang.Boolean java.lang.Boolean.valueOf(java.lang.String)
methodes 1 : public static boolean java.lang.Boolean.getBoolean(java.lang.String)
methodes 2 : public final native java.lang.Class java.lang.Object.getClass()
methodes 3 : public int java.lang.Boolean.hashCode()
methodes 4 : public boolean java.lang.Boolean.equals(java.lang.Object)
methodes 5 : public java.lang.String java.lang.Boolean.toString()
methodes 6 : public final native void java.lang.Object.notify()
methodes 7 : public final native void java.lang.Object.notifyAll()
methodes 8 : public final native void java.lang.Object.wait(long) 
             throws java.lang.InterruptedException
methodes 9 : public final void java.lang.Object.wait(long,int) 
             throws java.lang.InterruptedException
methodes 10 : public final void java.lang.Object.wait() 
              throws java.lang.InterruptedException
methodes 11 : public boolean java.lang.Boolean.booleanValue()
Il est aussi possible de créer des instances de classes à partir de la méthode newInstance envoyée à une objet de type Class. Cette méthode retourne un Object. Une fois un objet crée, on récupère ses champs par geField(name), et l'on peut accéder ou modifier la valeur de celui-ci (get et set).


Planche : 55


Chargeur de classes utilisateur
La machine virtuelle Java charge dynamiquement les classes dont l'exécution du programme en cours a besoin. L'option -verbose de l'interprète de byte-code de la machine abstraite Java. Habituellement la machine virtuelle Java charge une classe à partir d'un fichier local. Ce chargement peut être dépendant du système (variable CLASSPATH sous Unix, ...). Néanmoins il peut avoir des situations où les classes doivent être chargées de manière différentes : classes distantes (accessibles à partir d'un serveur sur le réseau), format de fichier spécifique, conversion à la volée, modification de la sécurité. Pour ces cas, il est nécessaire de définir une sous-classe de la classe abstraite ClassLoader pour étendre le comportement de chargement.

L'exemple suivant, tiré du tutorial de Java, montre comment créer un chargeur de classes pour le réseau. La classe NetworkClassLoader définit deux méthodes : loadClassData qui d'une URL retourne un tableau d'octets correspondant au code transmis et loadClass (seule méthode abstraite de la classe ClassLoader) pour le chargement effectif. Elle contient d'autre part une table de hachage pour connaître les classes déjà transférées. loadClass vérifie si le nom de la méthode est déjà dans la table de hachage, si ce n'est pas le cas, elle transfère les données et construit la classe à partir d'un tableau d'octets, stocke la classe dans la table de hachage puis déclenche resolveClass pour autoriser la création d'instances.

     class NetworkClassLoader extend ClassLoader {
         String host;
         int port;
         Hashtable cache = new Hashtable();
         private byte loadClassData(String name)[] {
         // load the class data from the connection
         ...
         }
         public synchronized Class loadClass(String name,
                                             boolean resolve) {
             Class c = cache.get(name);
             if (c == null) {
                 byte data[] = loadClassData(name);
                 c = defineClass(data, 0, data.length);
                 cache.put(name, c);
             }
             if (resolve)
                 resolveClass(c);
             return c;
         }
     }
Le code suivant montre comment créer une instance de la classe Main chargée dynamiquement par le nouveau chargeur.
ClassLoader loader= new NetworkClassLoader(host,port);
Object main= loader.loadClass("Main", true).newInstance();
...
Les navigateurs WWW, intégrant une machine virtuelle Java, implantent une sous-classe de ClassLoader (abstraite) pour le transfert via le réseau des classes et pour modifier la sécurité (d'où un changement de comportement entre appletviewer et netscape).


Planche : 56


Comparaison O'Caml et Java


Planche : 57


Noyau et implantation : Java
orienté objet (de classes)
syntaxe à la C
types de base, tableaux et objets (pas de pointeurs manipulables)
seulement des méthodes (pas de fonctions)
pas de classes paramétrées
exceptions (vues comme des objets)
récuperateur automatique de mémoire (GC)
processus légers
compilation vers du byte-code (portable)


Planche : 58


Noyau et implantation : O'Caml
extensions objet du noyau fonctionnel avec traits impératifs
type de données complexes (records, sommes,références) et classes
polymorphe paramétrique, classes paramétrées
exceptions comme valeurs
récuperateur automatique de mémoire (GC)
processus légers
compilation vers du byte-code (portable) et vers du code natif

Le langage Java est un langage objet à classe. Il possède aussi des types de base comme les entiers pour des questions d'efficacité. Par contre il n'y a pas de fonctions comme en C. Néanmoins les méthodes statiques peuvent jouer ce rôle.

Le langage O'Caml possède une extension objet au dessus de son noyau fonctionnel et impératif. Seule la liaison retardée est implantée dans la mesure où les fonctions et les modules simples permettent la liaison précoce.

Tous deux permettent la programmation multi-threading et ont un mécanisme d'exceptions permettant de propager des valeurs structurées.

Leur implantation sont assez proches. Les deux langages peuvent produire du code-octet pour une machine virtuelle. Les bibliothèques d'exécution possèdent un mécanisme de récupération de mémoire (GC). Dans les deux cas il n'y a pas de manipulation explicite des pointeurs bien que toute valeur structurée (y compris les objets) sont représentées par un pointeur vers le tas.

Le langage O'Caml possède un compilateur natif produisant un code efficace pour les principales architectures (Intel, Power-OC, Sparc, HP-PA ...).


Planche : 59


Typage : Java
polymorphisme d'inclusion et ad hoc (de surcharge)
surcharge résolue à la compilation
pas complètement typé statiquement
(vérification de type à l'exécution))
système de modules (correspondant à une hiérarchie de fichiers)


Planche : 60


Typage : O'Caml
typé statiquement
inférence de types
polymorphisme paramétrique et polymorphisme d'inclusion
contraintes de types explicites
système de modules paramétrés

Il manque à Java la notion de classes paramétrées (à la C++). Sa résolution de la surcharge peut échouer à la compilation. Le type de this n'apparaît pas dans le type d'une méthode.

Il manque à O'Caml un mécanisme de surcharge. Il est difficile à implanter. Cela est du principalement à l'inférence de types. De plus O'Caml possède l'héritage multiple.

bibliothèques et outils de développement
Java possède de très nombreuses bibliothèques fournies sous forme de classes ou de composants (Java Beans). On peut citer l'interface graphique (AWT), les classes réseaux (pour les clients/serveurs), les connexions aux bases de données (JDBC) et le mécanisme d'objets distribués (RMI). Les otuils de développement sont eux aussi nombreux et variés. Les principaux outils compatibles avec le jdk 1.1 sont JBuilder de Borland, Visual Age d'IBM. Ils intègrent des débogueurs, des outils de constructions d'interfaces graphiques et ne compilation rapide.
O'Caml a un environnement moins riche, mais ses bibliothèques sont distribuées avec les sources. On note principalement l'interface graphique CamlTk, les outils d'analyses syntaxiques (camllex, camlyacc et les streams intégrées au langage), une artihmétique à précision arbitraite sur les réels. Le débogueur pour les systèmes Unix permet de revenir à des points d'exécution dans le passé. Un environnement de développement a été développé pour le langage caml-light sous NextStep.
sécurité des applets
Les deux langages s'évertuent de construire des applets sûres. La différence principale ajoute de l'information de type au byte-code. Celle-ci sera vérifiée au chargement. O'caml ajoute une authentification cryptée pour garantir la provenance.

portabilité, interopérabilité, efficacité et disponibilité
La technique de compilation vers du byte-code rend les deux langages facilement portable sur différentes architectures de machine (Intel, Sparc, HP-PA, alpha, PowerPC, ...) et sur différents système (Windows 95/NT, Unix sous X-window, MacOS).
Les deux langages s'interfacent avec des programmes écrits en C.
O'Caml possède un compilateur natif pour les principales architectures de machine. Java permet de faire une expansion du byte-code Java en instructions de la machine hote (technique Just In Time). Les premiers compilateurs natifs de Java (j2c, assymetrix) n'étaient pas très convaincant, mais d'autres plus efficaces devraient suivre, en particulier la technique HotSpot de Sun.
O'caml est distribué gratuitement avec les sources à partir du site ftp de l'Inria et peut être redistribué en l'état et les extensions soit sous forme de bibliothèque, soit sous forme de fichiers patch indiquant les lignes modifiées par rapport à la version de distribution. Le navigateur MMM qui permet d'exécuter des applets en O'Caml est aussi distribué avec ses sources.
Le compilateur jdk de Sun est gratuit, mais les sources du système sont accordées sous licence. Les spécifications du langage sont publiques. Le navigateur Hotjava, développé en Java, est gratuit d'utilisation. Les sources des navigateurs Netscape et Explorer ne sont pas disponibles.

domaines d'applications
Ces deux langages ne jouent pas dans la même catégorie. Java est un langage qui vise à être utilisé dans les entreprises pour la majorité des développements (gestion, interface graphique, intranet, ...). O'Caml vient du monde académique et est principalement utilisé dans celui-ci : enseignement, recherche publique et privée. Néanmoins le langage de communication SCOL pour les mondes virtuels, de la socièté CRYO, s'inspire fortement du noyau fonctionnel de Caml.

Java et O'Caml peuvent être considérés comme des langages "généralistes" et peuvent répondre à la majorité des besoins pour le développement d'applications. industrielles, pour l'enseignement supérieur et pour les travaux de la recherche soit sur les langages, soit comme langage d'implantation.


Planche : 61


Liens

Ce document a été traduit de LATEX par HEVEA.