Skip to topic | Skip to bottom
Home
Minfo
Minfo.GlTD5y1314r1.6 - 06 Nov 2013 - 16:05 - PhilippeCollettopic end

Start of topic | Skip to actions

TD 5: chargement dynamique de classes, plugins

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).
      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

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

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

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

  • le chargement et la vérification des classes de plugin dans la classe PluginLoader (méthode loadOnePluginClass) ;
  • Le code pour récupérer le bon constructeur de Creature et pour créer une instance dans CreaturePluginFactory ;
  • La création des créatures et le lancement du simulateur dans l'écouteur du bouton "(Re-)start simulation" .

É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. Deux possiblités :

  • Désactivez la compilation automatique ("build automatically" dans le menu "project") et déplacez en dehors d'Eclipse les différentes classes de Creature dans le répertoire myplugins/repository après une première compilation.
  • ou exécutez 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 Launcher

Voici un schéma et quelques informations sur les différentes classes :

tp5-diag.png

  • PluginLoader :
    • Classe qui charge les plugins d'un type donné qu'elle trouve récursivement 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.

  • IPlugin :
    • 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. L'interface contient juste un nom, on récupère le nom de la classe dans l'implémentation fournie.

  • CreatePluginFactory :
    • L'usine à Creature, elle n'utilise quasiment plus de généricité, mais des objets Constructors (du package java.lang.reflect) qu'elle a obtenu à partir des classes de IPlugin fournies par le PluginLoader de Creature.

  • Launcher :
    • Initialise la CreatePluginFactory, crée des boutons qui pilote le chargement et la simulation (load, reload et redémarre la simulation).
    • Crée un menu à partir de la factory, en créant une entrée de menu par Creature chargée.

  • 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.

TIP CORRECTION: tp5-final.zip

-- PhilippeCollet - 06 Nov 2013
to top

I Attachment sort Action Size Date Who Comment
1.zip manage 1.3 K 01 Nov 2013 - 15:03 PhilippeCollet  
Exemple2.java manage 0.7 K 01 Nov 2013 - 15:03 PhilippeCollet  
Exemple3.java manage 0.7 K 01 Nov 2013 - 15:03 PhilippeCollet  
Exemple4.java manage 1.1 K 01 Nov 2013 - 15:03 PhilippeCollet  
Plugin.java manage 0.1 K 01 Nov 2013 - 15:03 PhilippeCollet  
Plugin1.java manage 0.2 K 01 Nov 2013 - 15:04 PhilippeCollet  
tp5-start.zip manage 37.5 K 04 Nov 2013 - 21:01 PhilippeCollet  
tp5-diag.png manage 153.8 K 05 Nov 2013 - 13:25 PhilippeCollet  
tp5-final.zip manage 47.1 K 06 Nov 2013 - 16:03 PhilippeCollet  

You are here: Minfo > GlTD5y1314

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