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.
Une application Java résulte de la collaboration de différents codes (dont certains peuvent être découverts au runtime).
Les objets ClassLoader
s chargent le code des classes.
Les ClassLoader
s 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 ClassLoader
s 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 ClassLoader
s 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"?
Pour accéder aux binaires il faut considérer plusieurs situations:
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 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.
![]() | 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). |
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
![]() | |
Ici la commande est lancée sous UNIX (notation "/" pour le séparateur d'éléments de cheminom au lieu de "\" sous win***) |
![]() | |
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. |
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).
voir ici la documentation de l’utilitaire jar (on notera en particulier la possibilité d’abriter différentes versions de code)
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 :
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)
jar cvfm monAppli.jar monManifeste.mf -C build/classes com/monbiz/finance
![]() | Attention |
---|---|
Si l’application est lancée en double-cliquant l’icône ou par la commande
|
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!
Une application utilise en général plusieurs 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:
--class-path
de java: on aura donc des codes rattachés au module sans nom
(unnamed module)
--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
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.
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
).