Skip to topic | Skip to bottom
Home
Minfo
Minfo.GlTD7r1.4 - 25 Nov 2012 - 21:30 - PhilippeCollettopic end

Start of topic | Skip to actions

TD 7 : Chargement dynamique de classes, plugins

PhilippeCollet, d'après des éléments de TP par MichelBuffa, PhilippeCollet et RichardGrin?

Introduction

Vous le savez peut-être, un programme Java est composé de classes qui sont chargées dynamiquement lorsqu'elles sont nécessaires. Certaines classes proviennent de votre disque local, d'autres proviennent peut-être du réseau (applet, etc...), certaines sont chargées automatiquement, d'autres peuvent l'être "à la demande".

Les questions sur le chargement des classes sont à réaliser hors d'Eclipse pour mieux maitriser la compilation des classes et le déplacement des fichiers .class nécessaires aux expérimentations.

Les classes ne sont pas toutes chargées en même temps : vérifions !

Avant tout, testons quelques exemples qui prouvent que toutes les classes utilisées par une appli Java ne sont pas chargées en mémoire avant que l'appli ne démarre.

  • Récupèrez donc les exemples de chargement dans cette archive.
  • Etudiez les classes A.java, B.java, C.java, D.java. Elles comportent toutes un bloc static qui affiche un message dès que la classe est chargée en mémoire. Compilez toutes les classes. Exécutez Exemple1.
  • Observez bien les chargements de chaque classe, et ce qui provoque ces chargements.
  • Remarquez (ClasseB n'est jamais chargée) que déclarer une variable d'un certain type ne suffit pas pour déclencher le chargement de ce type (classe ou interface). Les classes ne sont chargées qu'au moment où on en a vraiment besoin (chargement de ClasseD après les 5 secondes d'attentes). On appelle ça le "lazy loading" (chargement paresseux).
  • Remarque : pour des classes quelconques (sans bloc static comme celui de des classes A, B, C et D) on peut s'aider de l'option -verbose de la commande java pour observer les classes qui sont chargées.

Utiliser le chargement dynamique pour faire des plugins

En Java il est facile de charger une classe pendant l'exécution alors qu'on ne connait pas son nom quand on écrit le code. Ce nom peut être donné par l'utilisateur ou, le plus souvent, fourni par le contexte d'exécution. On peut ainsi parcourir un répertoire du disque local et charger toutes les classes du répertoire (celles qui correspondent à un fichier se terminant par ".class"). C'est le principe des plugins : on dépose des classes dans un répertoire et l'application les découvre et les charge au début de son exécution. Ces classes ajouteront des fonctionnalités à l'application.

  1. Étudiez la classe Exemple2. Étudiez en particulier les méthodes de la classe Class utilisée pour le chargement dynamique des classes (forName) et pour créer des instances (newInstance). Exécutez Exemple2.
  2. Exemple3 introduit la notion de plugin ; elle utilise l'interface Plugin et la classe Plugin1 (un plugin). Étudiez comment on peut faire appel à une méthode d'un plugin sans savoir au moment où on écrit le code le nom de la classe. Remarquez les exceptions qui sont attrapées. Le plus simple est d'utiliser une interface que tous les plugins devront implémenter. En passant, vous pouvez réviser vos fondamentaux : dans Exemple3, à quoi sert le cast de l'objet o en Plugin (p = (Plugin)o;) ? Ne pourrait-on écrire directement c.newInstance().decrisToi(); ?
  3. Mettez Plugin1 dans le paquetage fr.unice.plugin et refaites exécuter Exemple3. Pour que le plugin soit trouvé il faut positionner correctement Plugin car forName délègue la recherche des classes au chargeur de classes des applications. Ce chargeur de classes recherche dans le classpath, en tenant compte du nom du paquetage de la classe.

Recharger "à chaud" une nouvelle version d'un plugin

Il est un peu plus difficile de charger de nouvelles versions des plugins pendant la vie de l'application. C'est pourtant important de pouvoir le faire pour les serveurs qui tournent 24 h sur 24 pendant de longues périodes. Cette partie du TP vous montre comment charger à chaud une nouvelle version d'une classe déjà chargée.

  1. Vous allez travailler avec Exemple4. Cette classe reprend le code d'=Exemple3= mais place le code qui crée une instance de Plugin1 dans une boucle et ralentit l'exécution (par un Thread.sleep). Avant de lancer l'exécution de Exemple4, préparez à part une nouvelle version de Plugin1 avec une méthode decrisToi modifiée pour afficher un autre message. Lancez l'exécution de Exemple4 et, pendant l'exécution, vous remplacez Plugin1 par la nouvelle version. Est-ce que la modification est prise en compte ? Pourquoi ?
  2. Essayons de trouver un moyen de faire charger la nouvelle version de Plugin1.
    Voici quelques indications pour vous aider. Le problème est que
    • il est impossible de charger dans la JVM 2 versions différentes de la même classe ;
    • et il est impossible de décharger une classe dans la version actuelle de la JVM.
      Comment faire alors ? Il faut se rappeler le cours : 2 classes chargées par 2 chargeurs de classes différents sont considérées comme 2 classes différentes par la JVM, même si elles ont exactement le même nom complet (avec le nom du paquetage). Si vous ne le croyez pas, faites l'exercice optionnel placé à la fin du TP après avoir fini le TP (ne le faites pas tout de suite car le TP est long). Il suffit donc de faire charger la nouvelle version par un nouveau chargeur de classes et de récupérer une instance de cette classe nouvellement chargée pour faire le travail que l'on souhaite (afficher un nouveau message pour Plugin1, ).
      ALERT! IMPORTANT : pour cette question et la suivante, Plugin1 doit se trouver sous un répertoire plugins. Ce répertoire ne doit pas être dans le classpath, sauf si on le demande explicitement. Ce principe est à respecter pour tous les plugins si on veut pouvoir charger de nouvelles versions des plugins (la raison est donnée par la suite).
  3. Modifiez Exemple4 : faites charger Plugin1 par un nouveau chargeur à chaque fois. Pour cela, utilisez le plus simple, c'est-à-dire un URLClassLoader. Attention, n'oubliez pas de mettre un "/" à la fin de l'URL du répertoire où se trouve Plugin1. Le code pour la création du chargeur de classes :
    URLClassLoader cl = new URLClassLoader(new URL[] { new URL("file:plugins/") });
    c = cl.loadClass(...);
    
  4. Recommencez la manip du 1. et vérifiez que vous avez réussi en regardant le résultat de l'exécution de la méthode decrisToi.
  5. Ajoutez le répertoire plugins dans le classpath et refaites la question précédente (donc, ne tenez pas compte de l'avis "IMPORTANT" ci-dessus). Est-ce que la nouvelle version est chargée ? Pourquoi ?

ALERT! Indication : pensez à la délégation entre chargeurs de classes (en standard, un chargeur demande d'abord à son parent de charger la classe ; il ne charge lui-même une classe que si son parent ne l'a pas trouvée).

Application au simulateur de bébêtes

ALERT! Cette partie peut être effectuée dans Eclipse, mais il faudra faire quelques tests d'exécution en dehors pour déplacer les classes dans le répertoire de plugins.

Cette archive contient le source d'un simulateur de bébêtes avec plugins, avec des parties du code qui manquent et que vous allez écrire.

ALERT! si vous avez téléchargé l'archive avant le 26 novembre, rechargez la nouvelle version, avec un code de départ plus solide dans la gestion des threads.

Les parties qu'il vous reste à écrire sont :

  • le chargement des classes de plugin dans la classe ClassPluginLoader (méthode loadOnePluginClass) ;
  • le pilotage du chargeur depuis la classe FabriquePlugins (méthodes loadPlugins et reloadPlugins).

Écrivez ce qui manque, compilez et testez ce que vous avez écrit. Attention, pendant l'exécution les plugins ne doivent pas être dans le classpath pour ne pas fausser le chargement dynamique des classes. Déplacez donc BebeteStupide.class, BebeteEmergente.class et ChampiRouge.class dans le répertoire plugins/repository après la compilation. TIP Comme Eclipse a tendance a toujours tout recompiler dès que l'on sauve, le mieux est d'exécuter l'application depuis une ligne de commande après avoir déplacé les fichiers .class dans le répertoire de plugins. Par exemple, si les fichiers compilés sont dans le répertoire bin (défaut sous Eclipse) et que le répertoire de plugins est frère de ce répertoire, on peut se placer dans le répertoire parent :

$ java -cp bin TestPluginsBebetes

Le schéma suivant donne un aperçu général des nouvelles classes et interfaces du simulateur avec plugin.

schemaUML.jpg

Voici quelques informations sur les différentes classes

  • ClassPluginLoader :
    • Classe qui charge les plugins qu'elle trouve dans un répertoire. Ce répertoire est passé en paramètre au constructeur.
    • Elle conserve dans une liste la classe de chaque plugin chargé. On peut ensuite l'interroger pour lui demander cette liste.
    • Le chargement des classes est délégué à une instance de URLClassLoader qui est conservé dans une variable d'instance de PluginLoader?. Pour avoir les nouvelles versions des plugins, il suffit de ne plus garder les références aux anciens plugins dans la liste des plugins et d'utiliser un nouveau chargeur de classes pour recharger tous les plugins.

  • Plugin :
    • Les plugins sont des classes qui doivent implémenter l'interface Plugin. Les plugins peuvent être de différents types, représentés par une classe ou interface. Cette application utilise des plugins de type Champi et Bebete.

  • ATTENTION : Champi devient une interface ! Pourquoi ?

  • FabriquePlugins :
    • Une usine concrète, qui hérite donc de FabriqueEntites. Elle s'occupe de créer deux chargeurs de plugin, un pour les bébêtes, un pour les champis.
    • Elle récupère pour chaque plugin le "Constructor" qui lui permet d'implémenter la factory méthode correspondante (creeBebete, etc.)
    • Comme il peut y avoir plusieurs plugins de Bebetes (ou de champis), elle gère une liste et un indice dans la liste (par défaut 0). Les méthodes de création utilise le constructeur de plugin de l'indice courant. Cela permet de changer quelles bébêtes vont être créées par l'usine au fur et à mesure des (re-)chargements de plugins.

  • TestPluginsBebetes :
    • Initialise la FabriquePlugins, crée des boutons qui pilote la FabriquePlugins (load, reload et redémarre la simulation).
    • Un menu par type de plugin est créé et contient la liste des classes de plugin chargées. Des écouteurs sur les menus permettent de configurer la FabriquePlugins comme indiqué ci-dessus. Les menus sont mis a jour en cas de chargement/rechargement des plugins.

  • PluginMenuItemBuilder :
    • Petit utilitaire pour construire les menus des plugins.

  • Autre remarque : Le code est truffé d'appel à la classe Logger pour suivre facilement ce qui se passe à l'exécution. Pour voir les messages généré par le logger, il vous suffit de mettre en commentaire la première ligne de la méthode main de la classe principale.

Plugin SDK

Déterminez quel ensemble de classes minimum pourrait être diffusé à des développeurs de plugins afin de créer (compiler) leurs plugins.

Question complémentaire

2 classes chargées par 2 chargeurs de classes différents sont différentes, même si elles ont le même nom complet (avec le nom du paquetage)

  1. Placez ClasseD dans le répertoire plugins avec la classe Plugin,
  2. Compilez Exemple5 (mettez le répertoire courant "." et plugins dans le classpath),
  3. Faites exécuter Exemple5 qui contient le code suivant :
    URLClassLoader cl = new URLClassLoader(new URL[] { new URL("file:plugins/") }, null);
    Class c = cl.loadClass("ClasseD");
    System.out.println(c.equals(ClasseD.class));
    ClasseD o = (ClasseD) c.newInstance();
    
    Vous devriez avoir un message vous disant que les 2 ClasseD ne sont pas égales et, en plus, la JVM va refuser le cast en ClasseD car les 2 ClasseD n'ont rien en commun et ne sont pas filles l'une de l'autre. Est-ce que vous comprenez pourquoi ?

-- PhilippeCollet - 25 Nov 2012
to top

I Attachment sort Action Size Date Who down Comment
schemaUML.jpg manage 476.1 K 14 Nov 2012 - 15:33 PhilippeCollet  
1.zip manage 1.3 K 14 Nov 2012 - 15:34 PhilippeCollet  
Exemple2.java manage 0.7 K 14 Nov 2012 - 15:34 PhilippeCollet  
Exemple3.java manage 0.7 K 14 Nov 2012 - 15:34 PhilippeCollet  
Exemple4.java manage 1.1 K 14 Nov 2012 - 15:34 PhilippeCollet  
Exemple5.java manage 0.3 K 14 Nov 2012 - 15:34 PhilippeCollet  
Plugin.java manage 0.1 K 14 Nov 2012 - 15:34 PhilippeCollet  
Plugin1.java manage 0.2 K 14 Nov 2012 - 15:34 PhilippeCollet  
pluginDemarrage-safe.zip manage 55.4 K 25 Nov 2012 - 21:28 PhilippeCollet  

You are here: Minfo > GlTD7

to top

Copyright © 1999-2017 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding WIKIDeptinfo? Send feedback