Déploiement des codes

Attention!: ces chapitres demandent de l’attention car les pratiques sont à cheval sur deux mondes. On a d’une part les dispositions "historiques" de java (avant la modularisation) et d’autre part l’architecture modulaire.

Les deux mondes cohabitent (encore pendant quelque temps) et la principale difficulté est que chacun peut avoir des dispositifs particuliers. Vous serez donc appelés à comprendre les deux approches et à savoir les distinguer soigneusement.

Les ClassLoaders

images/bluebelt.png

Une application Java résulte de la collaboration de différents codes (dont certains peuvent être découverts au runtime).

Les objets ClassLoaders chargent le code des classes.

Les ClassLoaders ont une structure hiérarchique (qui reflète des priorités de choix quand on demande le chargement d’une classe -il est ainsi impossible d’exécuter un "cheval de troie" qui serait un code de librairie non-standard portant le nom d’un code standard-). Les applications peuvent créer leur propres ClassLoaders pour leurs besoins particuliers (chargement au travers du réseau, abandon de classes et remplacement par de nouveaux codes, etc…).

Quand on démarre une J.V.M il y a au moins 3 ClassLoaders actifs:

  • Le ClassLoader de bootstrap (Bootstrap class loader) qui charge une partie des classes standard du J.R.E. Il est intégré la JVM .

    Il définit les classes de modules critiques comme java.base (mais pas tous les modules de Java SE!).

  • Le ClassLoader de la plateforme (Platform class loader) qui charge les classes d’autres modules standard JavaSE (qui n’ont pas été chargées par le bootstrap). Au travers de ce ClassLoader toutes les classes de Java SE, y compris celles qui ont été chargées par le bootstrap, sont "visibles"; il en est de même d’un certain nombre d'extensions qui sont des bibliothèques approuvées par le Java Community Process.

    Ce ClassLoader peut être obtenu par la méthode ClassLoader.getPlatformClassLoader() lorsqu’on veut créer un ClassLoader (probablement un java.net.URLClassLoader) rattaché au sommet de la hiérarchie.

  • Le ClassLoader applicatif ( nommé improprement System ClassLoader) qui charge les autres codes (codes "applicatifs"). Ce chargement peut s’opérer de diverses manières et, en particulier, en explorant des "réceptacles" de classes dans le système de fichiers courant (ou au travers d’un réseau d’où URLClassLoader !).

    Comme nous allons le voir un ClassLoader est également capable d’aller chercher des données autres que des classes Java (notion de "ressource"). A ce propos un petit rappel sur une notion délicate: ne pas confondre les notions de "portée" (par ex. portée de package entre modules) et l’accessibilité par les ClassLoaders (on trouvera des ressources associées à des packages non exportés mais pourtant visibles pour le ClassLoader d’un autre module, mais aussi des ressources non "accessibles" parce que déployées incorrectement en dehors du module courant - voir plus loin l’exemple dans le chapitre "ressources" -)

Mais qu’entendons nous ici par "réceptacle"?

Quels réceptacles pour entreposer les codes ?

images/orangebelt.png

Pour accéder aux binaires il faut considérer plusieurs situations:

  • Quand on réalise des codes. Que ce soit pour les IDE (qui peuvent contrôler interactivement les A.P.I des classes que vous utilisez) ou pour le compilateur (qui a besoin de vérifier si votre utilisation des A.P.I.s est correcte). Les IDE et le compilateur accèdent aux binaires pour les analyser et reconnaître leurs A.P.I.s.
  • Quand on teste des codes. Ici nous avons un cas particulier où on peut écrire des codes de test qui n’appartiennent pas au module courant mais qui demandent un accès privilégié aux codes de ce module pour pouvoir les tester (il y a alors des options particulières de lancement qui permettent de contourner les problèmes de portée). Ce peut être des outils de test ou tout simplement des interactions avec Jshell.
  • Quand on exécute des codes.

Plusieurs possibilités sont offertes pour retrouver ces codes binaires:

  • Dans le système de fichier. Nous avons vu comment s’organise une hiérarchie de répertoires qui abritent des fichiers .class : les répertoires reflètent la hiérarchie des packages et prennent racine dans le répertoire du module courant. Ces répertoires de modules se trouvent listés dans l’argument --module-path (ou -p). Les éléments de la liste sont séparés par le caractère ; sous Win* et par : sous les Unix.

    Dans le cas où les modules ne sont pas utilisés on aura une liste de répertoires listés avec l’option --class-path (ou -classpath ou -cp ou encore dans la variable d’environnement CLASSPATH ) . Ces répertoires sont la racine des arborescences non modulaires liées aux packages.

  • Dans des archives JAR (ou éventuellement zip) les archives jar sont des conteneurs zip qui vont abriter des arborescences de fichiers. Ces arborescences sont analogues à celles que l’on trouve dans un système de fichier comme ceux décrits précédemment mais contiennent aussi des "méta-informations" qui sont décrites sous un répertoire de nom META_INF.

    Ce format est central dans les techniques de déploiement: il peut être aussi bien utilisé sous forme de fichiers sur la machine locale que sous forme de ressource distante accessible au téléchargement au travers du réseau (dans ce dernier cas un format plus optimisé du point de vue de la compression peut-être utilisé: pack200). Avec des I.D.E il faudra spécifier les jars que l’on veut rendre accessibles au développement.

    Si on utilise des modules on aura normalement un jar par module. Donc à la racine on trouvera le fichier module-info.class. L’option --module-path (ou -p) permet, en local, de trouver les répertoires où sont déposés les Jars que l’on veut rendre accessibles.

    Si on n’utilise pas de modules l’option -class-path permet de trouver:

    • soit explicitement des jars dont le chemin d’accès est spécifié dans la liste
    • soit implicitement tous les jars que l’on trouve dans un répertoire quand on termine le chemin par une étoile

      Un CLASSPATH UNIX. 

      -cp $HOME/java/classes:/opt/monAppli/monAppli.jar:$HOME/libsJava/*

      Un CLASSPATH WIN*. 

      -cp "%HOMEPATH%\java\classes;C:\Program Files\monAppli\monAppli.jar;%HOMEPATH%\libsjava\*"

  • Dans des "images" applicatives (JIMAGE) construites par l’utilitaire jlink. Ici il s’agit de créer un ensemble minimum qui contienne ce qu’il faut pour exécuter votre programme Java. On aura ainsi un runtime autonome avec juste le minimum de modules nécessaires à l’exécution de l’application.

    La création de cette image implique une phase d’optimisation des codes (appelée link_time) et permet de ne "livrer" qu’un minimum de choses. On a ici du code spécifique à une plateforme donnée (celle de la plateforme de développement) et il faut re-générer de nouvelles images quand la JVM connait des évolutions.

    Note: la création par jlink utilise un minimum de modules standard de JavaSE. Il est à noter que ces modules standard sont eux-mêmes déployés dans un format particulier Jmod. Ce format permet, entre autres choses, d’embarquer des codes natifs à la machine et ne peut être utilisé que pour des opérations de compilation ou d’assemblage (il ne peut être utilisé au runtime).

Nous verrons ultérieurement comment "livrer" sur site des applications basées sur ces formats.

[Note]Important: notion de "ressource"

En fait la livraison d’une application ne contient pas que des codes binaires Java. Pour fonctionner l’application peut avoir besoin d’autres types de données. Ce peut-être des images, des textes de configuration, des données de traduction des messages, etc. on qualifie généralement ces données de "Ressource" (attention en Anglais Resource … ne prend qu’un seul S ).

Un autre cas particulier peut se produire: des codes exécutables "natifs" spécifiques à une plateforme (ce point sera abordé beaucoup plus tard).

Création des archives jar sans modules

En supposant que vous soyez dans le répertoire qui sert de racine aux packages des binaires (le codebase) :

jar cvf monAppli.jar com

(ici "com" est la première racine de package -pour com.truc par ex.-)

Une commande plus complexe pour construire un jar avec une partie des codes de développement et avec des fichiers "ressources" situés dans un répertoire media.

jar cvf monAppli.jar -C build/classes com/monbiz/finance -C media images/gif

création de jar

[Note]

Ici la commande est lancée sous UNIX (notation "/" pour le séparateur d'éléments de cheminom au lieu de "\" sous win***)

[Note]

Si les fichiers ".class" ont été compilés avec toutes les options de debug (ce qui est le cas par défaut sous des IDE) il peut être utile de livrer des fichiers ".class" qui ont été recompilés avec des options plus légères. (par exemple en re-créant un autre projet qui partage les sources et qui génére les binaires avec des options limitées -par exemple uniquement en gardant la numérotation des lignes du code source-).

L’utilitaire Jlink dispose d’options spécifiques pour éliminer les données de debug.

Création des archives jar avec modules

Ici le principe est légèrement différent: chaque archive jar est associée à un module particulier.

La commande de création prendra comme racine le répertoire "racine" binaire du module (celui qui contient module-info.class (plus éventuellement des ressources additionnelles liées à ce module).

création de jar avec modules

voir ici la documentation de l’utilitaire jar (on notera en particulier la possibilité d’abriter différentes versions de code)

Lancement direct d’une application non modulaire depuis une archive jar

Lancer une application java en ligne de commande risque de ne pas être commode pour un utilisateur non averti. Sur un système d’exploitation correctement configuré on doit pouvoir lancer une application en double-cliquant sur l’icône d’un fichier jar.

Ceci ne fonctionnera toutefois que si le fichier jar décrit dans une meta-information le nom de la classe dont il faut invoquer le main.

Cette information se trouve dans le "fichier" META-INF/MANIFEST.MF dans l’archive jar .

On peut configurer l’archive de la manière suivante :

  • créer un fichier qui sera une matrice pour le manifeste: le nom est sans importance (par exemple monManifeste.mf)

matrice de fichier manifeste. 

Main-Class: com.monbiz.MonMain

(sur la plupart des systèmes ne pas oublier une ligne vide en fin de fichier)

  • lancer la commande de construction du jar
jar cvfm monAppli.jar monManifeste.mf -C build/classes com/monbiz/finance
[Avertissement]Attention

Si l’application est lancée en double-cliquant l’icône ou par la commande java -jar monAppli.jar le runtime ne tient plus compte du CLASSPATH. On doit alors créer une ligne Class-Path dans le fichier manifeste (et faire en sorte que la description correspondante soit portable : donc utiliser des cheminoms relatifs)

Lancement d’une application modulaire avec des archives jar

Attention: ici pas d’informations Main-Class dans le manifeste et pas de jar "clickable".

Les options java 9 peuvent être formalisées de manière différente:

jar --create --file monAppli.jar  --manifest monManifeste.mf -C modbin/com.monbiz.finance .

La création d’un jar avec un main doit être complétée:

jar --main-class=nomCanoniqueClasse autresoptions

Il est possible de lancer depuis la console une application abritée par des jars. En fait le module-path admet parfaitement des jars dans les répertoires de la liste. Donc:

java --module-path répertoireDesJars -m module

fonctionne (à partir du moment où l’option main-class a été spécifiée).

Il est possible que dans des versions ultérieures de java soit présentés des super-jars modulaires qui reconstituent la fonctionnalité du "jar clickable" … soyez à l’affut!

Déploiement d’archives jar

Une application utilise en général plusieurs archives jar:

  • les jars qui sont des librairies de composants
  • les jars applicatifs qui contiennent du code et des ressources immuables (noter que ces jars sont construits, signés et "scellés" par l'équipe de développement applicatif et ne doivent pas être modifiés au déploiement -ceci est également vrai pour les jars de composants-)
  • les jars contenant les codes et les ressources de déploiement (en général l'équipe applicative livre des modèles et des scripts de génération de ces jars).

déploiement complexe d’archives jar

Pour approfondir les questions techniques concernant les jars voir la documentation précitée.

Points principaux:

  • L’entrée Class-Path du manifeste permet de donner dans un jar la liste des jars dépendants (donc Class-Path: utils.jar deploy.jar etc…). Cette information est essentiellement pertinente pour les jars non-modulaires. Les informations du module-path sont à voir avec les options de lancement.

    En fait dès qu’on utilise de jar modulaires il vaut mieux régler les paramètres de l’exécution avec des options de lancement. Les jars non-modulaires complémentaires peuvent être déployés dans ce cas de deux manières différentes:

    • dans les répertoires pointés par l’option --class-path de java: on aura donc des codes rattachés au module sans nom (unnamed module)
    • dans les répertoires pointés par l’option --module-path de java: on aura alors des "modules automatiques". Ces "modules automatiques" abriteront de pseudo-modules dont le nom sera celui de l’archive jar (débarrassé du ".jar" et d'éventuelles informations de version -il est possible également de fixer le nom du module à partir du champ Automatic-Module-Name dans le manifeste). Ces modules pourront faire l’objet de directives requires de la part d’autres modules. Ils ont les mêmes droits d’accès que le module sans nom.
  • Chaque champ Name permet de fixer des propriétés des packages contenus: scellement mais aussi numéro de version du package (voir doc et l’exploitation possible par la classe java.lang.Package).

    Exemple

    Name: javax/xml/parsers
    Package-Version: 1.0.0
    Specification-Title:  Java API for XML Parsing
    Specification-Vendor: Sun Microsystems
    Sealed: true
    Specification-Version: 1.0.0
    Package-Vendor: Sun Microsystems, Inc.
    Package-Title: javax.xml.parsers
  • Une application qui est constituée de différentes parties indépendantes les unes des autres peut être déployée en utilisant un jar "maître" (pour le noyau de l’application) et différents jars pour les subdivisions de l’applications. Dans ce cas il est plus efficace d’indexer le jar "maître" (jar index) ce qui permet au ClassLoader d’optimiser ses recherches et ses chargements.

--exercices-- : La création et la mise en place des jars sera testée avec les chapitres suivants: services, ressources, internationalisation.

Création d’une image applicative (Jlink)

images/blackbelt.png

L’utilitaire jlink permet de créer une "image applicative" d’une application java. Cette "image" a l’avantage de permettre d’avoir sur un système donné une application java avec un minimum d’occupation d’espace (plus besoin d’avoir un JRE/JDK complet). L’inconvénient est qu’on a quelque chose de spécifique au système et qu’il faut mettre à jour cette image si le JRE évolue.

La commande Jlink n’est pas forcément accessible de depuis votre fenêtre de commande. Elle se trouve dans le sous-répertoire bin de la livraison java (donc le lancement peut s’effectuer par $JAVA_HOME/bin/jlink sous un système UNIX).

Les paramètres et options:

  • -module-path: liste de répertoires comprenant les binaires de modules ou des jars modulaires.

    exemple: --module-path com.maboite.monapp.jar:com.maboite.utils.jar:$JAVA_HOME/jmods ce dernier argument donne le répertoire où se trouve les fichier mods du JRE (fichier modulaire de génération). Noter ici le séparateur d'éléments de PATH : spécifique à UNIX (; sous Windows)

  • --add-modules spécifie la liste des modules à mettre dans l’image (éléments séparés par des virgules).

    Exemple: add-modules com.maboite.monapp,com.maboite.utils,java.base,java.logging (on a ici demandé les modules standard base et logging)

  • --limit-modules ici on reprend la liste de modules en insistant sur le fait que l’on ne veut pas voir apparaître toute la liste des modules qui serait déroulée si on suivait toutes les dépendances (en particulier les modules du JRE dont on n’a pas explicitement besoin dans notre application).

    Exemple: --limit-modules com.maboite.monapp,java.base,java.logging (pas plus!)

  • --launcher permet de spécifier un script de lancement qui pourra être directement lancé par un utilisateur (d’un simple clic!).

    Exemple: --launcher monApp=com.maboite.monapp/come.maboite.monapp.Main (ici on a désigné le code qui contient le main en donnant son module puis son nom canonique).

  • --output indique un nom de répertoire à créer pour abriter l’ensemble de l’image.

    Exemple: -output monApp : créera un répertoire de nom monApp … le script d’exécution (également monApp dans notre exemple) est dans le sous-répertoire bin.

Il existe encore d’autres options (par exemple optimisation de la compression ou suppression des informations de debug) Voir le mode d’emploi (et argument --help).