février
2009
Maintenant que nous avons vu comment représenter un module au niveau Java, nous allons voir comment charger dynamiquement ces modules dans notre application.
Au niveau de Java, les classes sont chargées depuis des ClassLoader qui comme son nom l’indique est un chargeur de classes.
De base, Java utilise le ClassLoader système pour charger les classes dont notre application a besoin et ce ClassLoader contient les classes de notre application et toutes les classe qu’il a pu découvrir dans le classpath qu’on lui a fourni.
Le problème est que dans notre cas, on ne peut pas vraiment ajouter les fichiers Jar des modules au classpath étant donné que l’application ne connait pas les modules.
De plus, on ne peut théoriquement pas modifier le ClassLoader système. Je dis théoriquement, parce qu’il est possible de rajouter des fichiers ou des ressources à un ClassLoader en utilisant la réfléction et en appelant une méthode non publiques, mais je ne trouve que cette méthode soit très propre.
Nous allons donc devoir créer un nouveau ClassLoader avec lequel nous allons charger les classe de nos modules.
Il nous faudra donc procéder en deux phases :
- La première servira à explorer les fichiers des modules, à en sortir la classe du module et à récolter les URLs des fichiers Jar
- La seconde va charger les différents modules en se servant d’un ClassLoader créé avec les URLs collectées durant la première phase.
On va donc créer une classe ModuleLoader qui va nous permettre d’effectuer ces phases.
Prenons maintenant la première phase et créons une méthode qui retourne la liste des classes à charger.
private static List<URL> urls = new ArrayList<URL>();
private static List<String> getModuleClasses(){
List<String> classes = new ArrayList<String>();
//On liste les fichiers de module
File[] files = new File("dossier").listFiles(new ModuleFilter());
for(File f : files){
JarFile jarFile = null;
try {
//On ouvre le fichier JAR
jarFile = new JarFile(f);
//On récupère le manifest
Manifest manifest = jarFile.getManifest();
//On récupère la classe
String moduleClassName = manifest.getMainAttributes().getValue("Module-Class");
classes.add(moduleClassName);
urls.add(f.toURI().toURL());
} catch (IOException e) {
e.printStackTrace();
} finally {
if(jarFile != null){
try {
jarFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return classes;
}
private static class ModuleFilter implements FileFilter {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().toLowerCase().endsWith(".jar");
}
}
}
Comme vous le voyez, ce n’est pas très compliqué, on parcours les fichiers Jar existant dans le dossier des modules, on ouvre le fichier Jar, on récupère le manifest et on récupère la classe du module. Ensuite de quoi, on ajoute encore l’URL du fichier Jar à la liste des URLs.
Bien entendu ce code est perfectible, il faudrait traiter le cas ou le fichier JAR n’a pas de manifest ou alors le cas ou le manifest n’a pas de classe de module et il faudrait traiter les erreurs correctement, mais ce n’est pas le but de ce billet.
On peut maintenant passer à la deuxième méthode qui va créer le ClassLoader et instancier les modules puis les retourner :
public static List<IModule> loadModules(){
List<IModule> modules = new ArrayList<IModule>();
List<String> classes = getModuleClasses();
AccessController.doPrivileged(new PrivilegedAction<Object>(){
@Override
public Object run() {
classLoader = new URLClassLoader(
urls.toArray(new URL[urls.size()]),
ModuleLoader.class.getClassLoader());
return null;
}
});
for(String c : classes){
try {
Class<?> moduleClass = Class.forName(c, true, classLoader);
if(IModule.class.isAssignableFrom(moduleClass)){
Class<IModule> castedClass = (Class<IModule>)moduleClass;
IModule module = castedClass.newInstance();
modules.add(module);
}
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return modules;
}
On commence donc créer un nouveau ClassLoader à partir des URLs qu’on a récupéré avec les fichiers JARs. On va utiliser un URLClassLoader qui permet de charger des classes depuis des emplacements définis par des URLs. On va lui donner comme parent le ClassLoader de la classe. Comme cela les modules qui utiliseront ce classloader pourront également utiliser les classes de l’application, ce qui est le but d’une application modulaire. Une fois qu’on a créé notre ClassLoader, on va parcourir toutes nos classes et les instancier si elles implémentent bien la bonne interface. Ensuite, on va pouvoir les ajouter à la liste et les retourner.
Encore une fois, ce code est perfectible, il faudrait traiter les erreurs et réagir aux différents cas possibles, mais j’ai essayé de faire au plus court.
Notre chargeur de modules est donc terminé.
Nous pouvons tester notre application en créant un Jar pour le module que nous avons défini dans le billet précédent et en créant un code tout simple pour les charger :
for(IModule module : modules){
System.out.println("Plug : " + module.getName());
module.plug();
}
System.out.println("Déroulement de l'application. ");
for(IModule module : modules){
module.unplug();
}
Voici ce que rendrait l’éxécution :
Plug : Simple module
Hello kernel !
Déroulement de l’application.
Bye kernel !
Comme vous le voyez, nous venons de développer une application modulaire. Bien entendu, il faut ensuite développer tous les services et les points d’extension, mais ces points sont spécifiques à l’application.
La technique présentée dans ce billet pose néanmoins un problème pour ce qui est du déploiement à la volée de module. En effet, si on charge un module après le chargement des premiers modules, on devra recréer un nouveau ClassLoader et on aura donc une partie des modules dans le premier ClassLoader et une partie des modules dans le second ClassLoader. Néanmoins, si les modules n’ont aucune intéraction entre eux, vous pouvez tout à fait envisager cette méthode pour le déploiement à la volée. Si ce n’est pas le cas, vous serez obligé de vous rabattre sur la méthode avec réfléction pour ajouter des modules dans votre ClassLoader. Je présenterai peut-être cette méthode dans un prochain billet.
Le fait d’avoir un deuxième ClassLoader pose également un problème pour les librairies qui chargent des classes à la volée comme par exemple Spring IOC ou Hibernate. Il faut voir au cas par cas pour ces librairies comment on peut leur spécifier d’utiliser notre ClassLoader. Souvent ceci est faisable en spécifiant le contextClassLoader via la méthode Thread.currentThread().setContextClassLoader(ClassLoader cl).
8 Commentaires + Ajouter un commentaire
Archives
- novembre 2011
- avril 2010
- mars 2010
- février 2010
- janvier 2010
- décembre 2009
- novembre 2009
- octobre 2009
- septembre 2009
- juillet 2009
- juin 2009
- avril 2009
- mars 2009
- février 2009
- octobre 2008
- septembre 2008
- mars 2008
- février 2008
- janvier 2008
- décembre 2007
- novembre 2007
- octobre 2007
- septembre 2007
- août 2007
- juillet 2007
- juin 2007
- mai 2007
- avril 2007
Catégories
- AMD
- Apple
- Cartes graphiques
- Chrome
- Conception
- Divers
- Eclipse
- English
- Hardware
- Informatique générale
- Intégration continue
- IntelliJ Idea
- Java
- JTheque
- Linux
- Logiciels
- Mes articles
- Mes critiques de livres
- Mes projets
- Microsoft
- Mon serveur perso
- Office 2007
- Open Source
- Outils
- Perso
- PHP
- Processeurs
- Programmation
- Sécurité
- Spring
- Windows Vista
- Windows XP
Bonjour,
C’est très certainement possible à faire avec Eclipse, néanmoins je ne m’y connais pas du tout, aussi, je vous invite à consulter les articles qui sont présents sur Developpez.com ou à poser votre question sur le forum.
Désolé…
Baptiste
Bonjours,
J’ai un soucis de dépendance de classe lors du démarrage de mon application Eclipse.
En fait, j’ai créé un point d’extensions. Et les classes qui sont contenues dans les plugins connectés sont inconnu lors de la phase de dé-sérialisation.
J’ai donc tenté de réutiliser cette méthode, mais pour un projet Eclipse. Sans trop de succès.
Pour accéder au différents plugins, on a la liste des points d’extension
« IConfigurationElement[] contributions = Platform.getExtensionRegistry().getConfigurationElementsFor(
EXTENSION_POINT_ID); »
Mais pas la liste des jars !!
Je n’arrive donc pas a recréer le fichier, soit l’équivalent a « urls.add(f.toURI().toURL()); »
Est il possible de transcrire cette méthode pour des points d’extension ?
Merci pour l’article.
Merci
Non, effectivement, ce n’est pas pratique à débugger. Je recompilais toujours le module que je testais (au moyen de scripts ants, c’est déjà ça de gagné) avant de pouvoir relancer l’application.
De plus, on n’arrive pas non plus à utiliser le Debugger d’Eclipse avec ça
Mais ça reste faisable à développer, bien qu’effectivement plus contraignant qu’une application normale à développer.
C’est exactement ce que je recherchais pour un projet en cours, merci pour ta contribution sur la modularité (modularisation ?)
Cependant, sous Eclipse, ce n’est pas très pratique, pour debugger, ça oblige à recompiler le jar pour recharger le module incriminé… Y a-t-il une solution ou faut-il se faire une « passerelle » temporaire ?
Merci en tout cas, bon boulot !
@Djo : Pour ta première question, sinok a parfaitement répondu Ces classes seront ensuite chargées normalement au moyen du ClassLoader qui a servi à instancier la classe du module.
Pour ce qui est de la visibilité des classes, je ne la gère pas. Tous les modules pourraient utiliser tous les autres modules en connaissant la classe. Je ne vois pas vraiment comment gérer cette visibilité moi-même C’est là que ma technique montre ses limites par rapport à OSGi par exemple.
@djo> L’URLCLassLoader charge l’intégralité des classes contenues dans le jar, wicht lui n’instancie que les classe qui l’intéresse.
@zeth> Le ClassLoader courant ne cherche les ressources que dans le classpath, pas ailleurs, dans le cas d’un système de modules on ne peut pas ajouter chaque module au classpath, ce serait tout juste infernal.
question peut être idiote mais pourquoi ne peut on pas utilisé le classloader courant ?
Très instructif Baptiste
Surtout vers la fin, pour ce qui du problème des classloarders.
Sinon, quelques remarques:
– Tu ne charges que les classes modules des jars : mais si cette classe de module dépend d’autres classes définies dans ce module (et qui ne sont pas chargées) ?
– Comment gères tu la visibilité externe des classes d’un module ? Elles sont toutes visibles normalement, non ?