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.GlTD7
r1.4 - 25 Nov 2012 - 21:30 -
PhilippeCollet
topic end
Start of topic |
Skip to actions
---+ TD 7 : Chargement dynamique de classes, plugins Main.PhilippeCollet, d'après des éléments de TP par Main.MichelBuffa, Main.PhilippeCollet et Main.RichardGrin %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). 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, ). <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 de bébêtes %X% 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. [[%ATTACHURL%/pluginDemarrage-safe.zip][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. %X% *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. %T% *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 : <verbatim> $ java -cp bin TestPluginsBebetes </verbatim> Le schéma suivant donne un aperçu général des nouvelles classes et interfaces du simulateur avec plugin. <img src="%ATTACHURLPATH%/schemaUML.jpg" alt="schemaUML.jpg" width="1325" height="751" /> 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 [[%ATTACHURL%/Exemple5.java][Exemple5]] (mettez le répertoire courant "." et plugins dans le classpath), 3 Faites exécuter =Exemple5= qui contient le code suivant : <verbatim> 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(); </verbatim> 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 ? -- Main.PhilippeCollet - 25 Nov 2012
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.4 |
>
|
r1.3
|
>
|
r1.2
|
Total page history
|
Backlinks
You are here:
Minfo
>
GlTD7
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