Où va Java ? Java Module System


Les archives JAR sont bien connu des développeurs Java puisqu’elles représentent le format de distribution des librairies et des applications Java. Mais ce format qui date du milieu des années 90 n’est plus très bien adapté à son rôle puisqu’il n’est pas aisé à distribuer ni à gérer, si bien qu’on parle de « jar hell » (« l’enfer du jar »).

Actuellement il n’y a pas à proprement parler de gestion des librairies. Chaque application doit fournir l’emplacement des librairies qu’elle utilise lors de son exécution, généralement en modifiant son CLASSPATH, c’est à dire que toutes la gestion des dépendances et des versions est à gérer lors du lancement de l’application, ce qui est spécifique au système d’exploitation. Bref c’est fastidieux et cela demandent des traitements spécifiques pour chaque système d’exploitation.

Il y a bien une alternative consistant à « installer » les librairies comme extension de la machine virtuelle, en les plaçant dans le répertoire lib/ext du JRE, mais étant donnée qu’il n’y a aucune gestion des versions cela peut poser de nombreux problèmes, y compris pour d’autres applications qui spécifieraient explicitement leurs propres librairies, car différentes versions de la même librairie pourraient se retrouver présente en même temps. Or dans ces cas là il n’est pas possible de déterminer la version qui sera utilisée, et on pourrait même se retrouver à utiliser partiellement deux versions différentes de la même librairies (ce qui aboutira généralement à une belle exception peu évidente).

On peut également retrouver le problème inverse : une application avec plusieurs niveaux de dépendances peut être amener à devoir utiliser deux versions incompatibles de la même librairie, par exemple si elle utilise les librairies A et B qui nécessitent toutes deux des versions différentes de la librairie C. Cela est totalement impossible actuellement (mais le problème est limité par le fait que les librairies Java externes conservent généralement elles-aussi une compatibilité ascendante).

Java 7 tentera d’apporter une solution à tous ces problèmes via Java Module System, qui permettra de simplifier la création de modules (applications et librairies) en intégrant le support du déploiement dans la plateforme Java et en corrigeant les principales lacunes relatifs aux jars.

Java Module System a une architecture contenant quatre principaux composants permettant de manipuler les librairies/applications en tant que « modules » :

  • Un format de distribution (JAM), qui a pour objectif de combler les lacunes du format JAR.
  • Une gestion des versions permettant de spécifier les dépendances aux autres modules.
  • Des référentiels pour le stockage, la recherche et la récupération des modules.
  • Le support de tout cela au sein du mécanisme de chargement des classes de la machine virtuelle.

JAM : JAR 2.0

JAM est une évolution du format JAR, avec lequel il reste d’ailleurs compatible (une archive JAM pourrait être utilisée comme une archive JAR avec des JRE plus anciens). En effet une archive JAM n’est rien de plus qu’un archive JAR complétée de meta-données représentant un « module ». Concrètement il s’agit simplement d’un JAR contenant un répertoire MODULE-INF avec un fichier METADATA.module contenant des informations spécifiques a ce module.

Ces informations comporte en particulier un certain nombre d’éléments obligatoires :

  • Name : qui permettra donc d’identifier le module avec un nom unique (reprenant la convention de nommage des packages, c’est à dire en utilisant un nom de domaine inversé).
  • Imports : qui listera les modules externes utilisés par ce module, avec éventuellement une contrainte sur le numéro de version.
  • Class exports : qui listera les classes exportées par ce module (c’est à dire les classes visibles par les autres modules).
  • Members : qui listera toutes les classes qui font partie de ce module, y compris si elle ne sont pas exportées).

Les méta-données des modules pourront également contenir des informations supplémentaires, parmi lesquelles on retrouve :

  • Version : qui associera un numéro de version spécifique à ce module, comme défini dans la section suivante.
  • Main Class : qui permettra de spécifier le nom de la classe principale dans le cas d’une application (tout comme l’attribut du manifest du même nom).
  • Resource Exports : qui défini la liste des ressources contenus dans l’archive et visible depuis l’extérieur.
  • Attributes : qui permettra de d’associer au module un ensemble d’attribut sous la forme « clef=valeur ».

A noter qu’il est probable le type de meta-données puisse être étendu par la suite.

Vous aurez sans doute remarqué certaines ressemblances avec les superpackages, en particulier en ce qui concerne les méta-données obligatoire. Ceci n’est pas du tout un hasard puisque un module sera en fait basé sur un superpackage et que le fichier METADATA.module sera généré automatiquement lors la phase de compilation.

Ainsi la déclaration du superpackage permet de renseigner tous les champs obligatoires du module : le nom du module correspondra à celui du superpackage, et son contenu permettra de renseigner la liste des modules importés, des classes exportées et des classes membres. En plus de cela, un certain nombre d’annotations permettront d’insérer les informations optionnels. Par exemple on pourra utiliser l’annotation @Version pour préciser la version du module, ainsi qu’une annotation @VersionConstraint à utiliser lors de l’importation d’autre module/superpackage pour définir des contraintes de version.

Ainsi par exemple pour définir un module en version « 1.0″ qui utilise deux autres modules avec des contraintes sur les version, on pourra utiliser le code suivant :


// Déclaration du superpackage :  
@Version("1.0")      // Version du module
superpackage com.site.pack {  // Nom du module
 
  // Importation du module com.site.name en version 1.0 minimum :
  @VersionConstraint("1.0+")
  import com.site.name;
 
  // Importation du module com.site.xml de la famille 1.5 :
  @VersionConstraint("1.5*")
  import com.site.xml;
 
  // Liste des packages qui appartiennent à ce superpackge :  
  member package com.site.pack, com.site.pack.impl;  
 
  // Liste des types exportés (visible en dehors du package) :  
  export com.site.pack.*;  
 
}

Il y aura bien sûr plusieurs autres annotations permettant de renseigner les informations complémentaire du modules, comme @ExportResources, @ModuleAttributes, @MainClass et d’autres.

En plus de toutes ces méta-données, le format JAM apporte d’autres avantages par rapport aux JARs :

  • Il peut contenir des librairies natives dans les répertoires /MODULE-INF/bin/<platform>/<arch>/, où <platform> et <arch> représentent le système (exemple : windows ou linux) et l’architecture (exemple : x86 ou x86-64). La machine virtuelle se chargera de faire tout le neccessaire pour que ces librairies soient correctement chargées via System.loadLibrary().
  • Il peut lui-même être spécifique à une plateforme et une architecture particulière. Un module du même nom peut ainsi être décliné selon la plateforme cible, et être utilisable seulement sur cette dernière.
  • Il est possible d’y inclure des JARs standard, dont le contenu pourra être exporté ou utilisé comme s’il s’agissait de classes ou ressources présente directement dans l’archive JAM.
  • Les archives JAM pourront être compressés avec le format de compression Pack200-gzip plus performant.

Versioning

La gestion des versions est un élément de base du Java Module System. Ainsi chaque module pourra se voir attribuer un numéro de version de la forme suivante :

  major[.minor[.micro[.update]]][-qualifier]

Les champs major, minor, micro et update correspondent à une valeur entière et représentent un niveau d’importance de la version. La champ major représentant à des changements important (et potentiellement incompatible) alors que le champ update représente des changements mineures (par exemple une correction de bug qui n’implique aucune compatibilité). Les valeurs intermédiaires permettrons de spécifier des changements de versions intermédiaires.

Enfin le champ qualifier correspond à un libellé quelconque, qui pourrait être utilisé en cours de développement pour différencier les différentes compilations et évolutions d’une même version.

Ainsi, il sera possible de spécifier des numéro de version de la sorte : « 1″, « 1.7″, « 1.7.0.1″, « 1.7.0.2″, « 1.7.1″, « 1.7.2-rc »…

Lors de l’utilisation d’un autre module, il sera dorénavant possible de définir des contraintes concernant les versions qui pourront être utilisées. Ces contraintes peuvent prendre la forme des trois syntaxes suivantes :

  • La syntaxe open se contentera de définir un numéro de version minimum suivi du caractère ‘+‘, et n’importe quelle version supérieure pourra être utilisé. Par exemple avec « 1.5+ » on pourra utiliser toutes les versions supérieurs ou égales à « 1.5″ (par exemple : « 1.5.2″, « 1.6″, « 2.0″, etc.).
  • La syntaxe family limitera la compatibilité à une famille de version, en utilisant le caractère ‘*‘. Une famille correspond à toutes les versions commençant par le même numéro. Par exemple « 1.5* » représentera seulement les numéro de version commençant par « 1.5″ (par exemple « 1.5.2″, « 1.5.2.1″, « 1.5.4″, etc.) mais interdira toutes versions supérieur comme « 1.6″ ou « 2.0″…
  • Enfin la syntaxe open with family : permettra de définir un numéro de version minimum au sein d’une famille. En utilisant des crochets pour délimiter la famille de la version minimum. Ainsi « 1.[5+] » représentera toutes les versions supérieurs ou égales à « 1.5″ au sein de la famille « 1* » (par exemple « 1.5.2″, « 1.6″, « 1.7″, etc.) mais interdira toutes versions supérieurs à la version « 1* » (« 2.0″ donc).

La tableau suivante donne un petit récapitulatif de tout cela :

Syntaxe Description Formule
1+ Version 1 ou supérieur (sans contrainte) 1 <= x
1.5+ Version 1.5 ou supérieur (sans contrainte) 1.5 <= x
1.5.2+ Version 1.5.2 ou supérieur (sans contrainte) 1.5.2 <= x
1* Toutes versions de la famille 1 1 <= x < 2
1.5* Toutes versions de la famille 1.5 1.5 <= x < 1.6
1.5.2* Toutes versions de la famille 1.5.2 1.5.2 <= x < 1.5.3
1.[5+] Version 1.5 ou supérieur dans la famille 1.0 1.5 <= x < 2.0
1.5.[2+] Version 1.5.2 ou supérieur dans la famille 1.5 1.5.2 <= x < 1.6
1.[5.2+] Version 1.5.2 ou supérieur dans la famille 1.0 1.5.2 <= x < 2.0

Il sera ainsi possible d’associer des librairies externes en gérant finement les versions utilisables, mais surtout il sera possible d’utiliser plusieurs versions différentes d’un même module.

A noter qu’il sera également possible d’utiliser une classe Java qui s’occupera des règles d’importations dynamiquement lors de l’exécution, et permettre ainsi un contrôle total des librairies.

Référentiel

Le terme référentiel (« repository ») regroupe en fait tout le mécanisme de stockage, recherche et chargement des différents modules et de leurs définitions. C’est à lui que reviendra la charge de rechercher et charger les modules selon leurs versions.

Chaque application possèdera au minimum un référentiel qui exposera l’API standard de Java, et qui sera initialisé par la JVM. Mais en plus de cela chaque application pourra posséder d’autres référentiels, comme par exemple un référentiel global partagé par toutes les JVM et qui contiendrait divers modules externes. Ces derniers seront ainsi simplement utilisable par toutes les applications.

Autre exemple : une application pourra définir son propre référentiel contenant des modules qui lui sont spécifique.

Enfin, un référentiel pourra permettre, en plus de la recherche et du chargement de module, d’installer ou supprimer des modules.

Ils devrait y avoir deux principaux types de référentiel :

  • Les « Local Repository » qui manipuleront les modules d’un répertoire présent en local sur le système.
  • Les « URL Repository » qui manipuleront des modules distants (par HTTP par exemple), et qui les téléchargeront dans un cache local selon le besoin.

Il sera ainsi possible de rajouter des référentiels en ligne de commande lors du lancement de l’application, ou dynamiquement pendant son exécution via l’intégration d’une API spécifique.

Ces référentiels devront donc remplacer la notion de CLASSPATH actuellement utilisée pour le chargement des classes, tout en permettant bien plus de contrôle (gestion des versions et des dépendances, installation « à la volée », isolations, etc.).

Avis

Les archives JAM apportent de nombreuses réponses aux problèmes posées par les archives JAR, et les mécanismes introduit devrait permettre un déploiement et une utilisation plus souple de ces librairies/modules, en particulier grâce aux référentiels et au mécanisme de versioning.

A l’instar des superpackages, Java Module System est entrée dans le cadre du JCP via la JSR 277 et devrait normalement être présent dans Java 7. On retrouve d’ailleurs dans le cadre de cette JSR un document Early Draft Review de plus de 150 pages décrivant tous les mécanismes qui seront mis en place (et qui sont difficile à détailler en quelques lignes).

Il ne reste plus qu’à voir les impacts que ces changements pourraient avoir sur la plateforme Java, et si la migration sera si simple que promise…

8 réflexions au sujet de « Où va Java ? Java Module System »

  1. Avatar de vdemeestervdemeester

    En gros, ça devrait nous donner plus ou moins quelque chose de plus ou moins proche de ce qui se fait en Python, et ça, je dois avouer que c’est une des grosses raison pour laquelle je préfère python (c’est pas la seule :)).

    En tout cas, c’est une bonne nouvelle :D

  2. alexdp

    Il aura fallu 10 ans à Java et la naissance de produits populaires comme Maven ou Ivy pour que Sun réagisse enfin. Attention tout de même, comme le dit mon ami Marco : « le mamouth a levé l’oreille mais il n’a pas bougé la patte ».

  3. adiguba Auteur de l’article

    Le numéro de version est dans le code, mais dans un fichier particulier (qui devrait se nommer « super-package.java ») qui se contentera de regrouper les informations sur le superpackage (et donc le module).

    Perso je ne trouve pas cela choquant ;)

    a++

  4. denisC

    Ca me rappelle beaucoup tout ce que fait Maven, ça, tiens…..

    Par contre, le numéro de version est dans la description du superpackage, donc dans le source?

    Pourquoi ne pas avoir utilisé quelque chose de plus proche de l’existant (osgi ou maven)?

  5. ludosoft

    Et avec ceci le risque de devoir déployer des applications qui vont embarquer 3 ou 4 versions de la même bibliothèque.

    Cependant on peut espérer compter sur la sanction de la communauté pour limiter des réaction du type « bourf, ça marche au top avec la v1.2, j’vais pô m’em… avec la 1.3 hein ».

Laisser un commentaire