Skip to topic
|
Skip to bottom
Jump:
Minfo
Minfo Web
Minfo Web Home
Changes
Index
Search
Webs
Accueil
Docs
Enseignants
Linfo
MIPS
Main
Minfo
Minfo03
Minfo04
Minfo05
Minfo06
Minfo07
Private
Projets
Sandbox
TWiki
Create
personal sidebar
Edit
Attach
Printable
Minfo.GlTD5y1314
r1.7 - 15 Nov 2014 - 22:17 -
PhilippeCollet
topic end
Start of topic |
Skip to actions
---+ TD 5: chargement dynamique de classes, plugins %TOC% ---++ 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 [[%ATTACHURL%/1.zip][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 (<nop>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 <nop>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 [[%ATTACHURL%/Exemple2.java][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 [[%ATTACHURL%/Exemple3.java][Exemple3]] introduit la notion de plugin ; elle utilise l'interface [[%ATTACHURL%/Plugin.java][Plugin]] et la classe [[%ATTACHURL%/Plugin1.java][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 [[%ATTACHURL%/Exemple4.java][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=. <br> 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. <br> 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). <br> %X% *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 : <br> <verbatim> URLClassLoader cl = new URLClassLoader(new URL[] { new URL("file:plugins/") }); c = cl.loadClass(...); </verbatim> 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 ? %X% *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 %X% 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. [[%ATTACHURL%/tp5-start.zip][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 : <verbatim> $ java -cp bin Launcher </verbatim> Voici un schéma et quelques informations sur les différentes classes : <img src="%ATTACHURLPATH%/tp5-diag.png" alt="tp5-diag.png" width="1280" height="538" /> * =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. %T% *CORRECTION*: [[%ATTACHURL%/tp5-final.zip][tp5-final.zip]] -- Main.PhilippeCollet - 06 Nov 2013
to top
End of topic
Skip to action links
|
Back to top
Edit
|
Attach image or document
|
Printable version
|
Raw text
|
More topic actions
Revisions: | r1.7 |
>
|
r1.6
|
>
|
r1.5
|
Total page history
|
Backlinks
You are here:
Minfo
>
GlTD5y1314
to top
Copyright © 1999-2021 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding WIKIDeptinfo?
Send feedback