JBoss Modules

JBoss Modules est le système de gestion des modules intégré à WildFly. Il est plus simple qu’OSGi et utilisable dans WildFly ou dans une application autonome dès 2012.

Cette page peut vous intéresser pour construire des modules pour WildFly ou pour développer des applications à base de modules.

Structure d’un module

Un module peut être un fichier jar avec une description dans le fichier manifest, un peu comme pour OSGi, ou dans un répertoire avec une description XML.

La forme utilisée dans WildFly est celle du répertoire : par exemple, le module org.sewatech.exemple sera dans un répertoire org/sewatech/exemple. Dans ce répertoire, on aura un sous-répertoire par slot. Un slot correspond à une version et le slot par défaut est dans le répertoire main. Le contenu du slot par défaut du module org.sewatech.exemple est donc dans le répertoire org/sewatech/exemple/main.

Ce répertoire contient au moins un fichier module.xml qui décrit le contenu du module, sous forme de ressources (fichiers jar) et ses dépendances.

 <?xml version="1.0" encoding="UTF-8"?>
 <module xmlns="urn:jboss:module:1.1" name="org.sewatech.this.one">
   <resources>
     <resource-root path="first.jar"/>
     <resource-root path="second.jar"/>
   </resources>

   <dependencies>
     <module name="org.sewatech.other" />
   </dependencies>
 </module>

Les ressources sont des fichiers jar tout à fait standards, sans aucune référence à JBoss Modules. L’association entre le fichier jar et la notion de module est faite exclusivement dans le fichier module.xml.

Module de démarrage

Pour développer une application autonome, en dehors de WildFly, il faut une classe avec une méthode main qu’on va déclarer comme classe de démarrage du module.

 <?xml version="1.0" encoding="UTF-8"?>
 <module xmlns="urn:jboss:module:1.1" name="org.sewatech.start">
   <main-class name="org.sewatech.start.Main"/>
   ...
 </module>

Ensuite, il faut démarrer Java avec jboss-modules, en indiquant que ce module est utilisé au démarrage.

 java -jar jboss-modules.jar -mp modules org.sewatech.module.start

L’option -mp permet de donner le répertoire racine des modules.

Export

Par défaut, un module exporte tout le contenu de ses ressources aux modules qui vont l’utiliser. Pour limiter l’accès à une API, et interdire l’accès à des classes d’implémentation, le module peut exporter uniquement certains packages.

 <?xml version="1.0" encoding="UTF-8"?>
 <module xmlns="urn:jboss:module:1.1" name="org.sewatech.other">
   <exports>
   </exports>
   ...
 </module>

Dépendances

On doit définir explicitement les dépendances pour utiliser des classes externes au module. Seules les classes de packages java.* sont utilisables directement. Pour toutes les autres classes, même celles du JDK ou celles dans le classpath doivent être déclarées. C’est le cas, par exemple pour JAXB ou l’API module. Dans ce cas, on parle de dépendances system.

Dépendances systèmes

Une dépendance système fait référence à un ou plusieurs package présents dans le classpath.

 <dependencies>
   <system>
     <paths>
       <path name="org/jboss/modules"/>
       ...
     </paths>
   </system>
 </dependencies>

Dans les exemples cités précédemment, les dépendances minimales sont sur les packages javax.xml.bind et com.sun.xml.internal.bind.v2, pour les classes, et javax/xml/bind/annotation pour les annotations. Les erreurs pour les classes sont assez parlante (java.lang.ClassNotFoundException) alors que pour les annotations, c’est un peu plus flou : " SAXException2: unable to marshal …​ ". Ceci est plus dû au fonctionnement de ces annotations qu’à celui des modules. En effet, lorsqu’un annotation ne peut pas être chargée par le classloader, elle est tout simplement ignorée.

 <dependencies>
   <system>
     <paths>
       <path name="javax/xml/bind"/>
       <path name="javax/xml/bind/annotation" />
       <path name="com/sun/xml/internal/bind/v2"/>
     </paths>
   </system>
 </dependencies>

On voit bien qu’il peut être fastidieux de devoir déclarer toutes ces dépendances. Cette tâche peut être simplifiée en utilisant les modules fournis dans WildFly. Le module javax.api centralise les dépendances vers les packages javax.* de Java SE et sun.jdk vers les packages com.sun.* et sun.* fournis avec la JVM d’Oracle. On notera l’attribut export="true" dans ces deux modules…​

Export

Lorsqu’un module A déclare une dépendance vers un autre module B, il peut en utiliser toutes les classes et interfaces. En revanche, la dépendance n’étant pas transitive, il ne peut pas utiliser les classes des modules d’un troisième module, même si B a déclaré une dépendance vers lui. Si la transitivité est utile pour un module, elle doit être déclarée explicitement. C’est ce qui est fait par exemple dans le module javax.api que j’ai cité plus haut : ce module dépend de ressources system, qu’il exporte aux autres modules :

   <dependencies>
     <system export="true">
       <paths>
           <path name="javax/accessibility"/>
           ...
       </paths>
     </system>
   </dependencies>

De la même façon, on peut exporter un module, totalement ou partiellement. Pour un import total, on ajoute l’attribut export="true" au module en dépendance :

   <dependencies>
     <module export="true" name="org.sewatech.other" />
     ...
   </dependencies>

Pour un export partiel, on ajoute un sous-élément <export> dans le module en dépendance pour inclure ou exclure des packages :

   <dependencies>
     <module name="org.sewatech.other">
       <export>
         <include-set>
           <path name="org.sewatech.other.api"/>
         </include-set>
       </export>
     </module>
     ...
   </dependencies>

ou

   <dependencies>
     <module name="org.sewatech.other">
       <export>
         <exclude-set>
           <path name="org.sewatech.other.impl"/>
         </exclude-set>
       </export>
     </module>
     ...
   </dependencies>