3. Archives, chemin de recherche, classpath
La machine Java sait aller chercher des fichiers classe dans une structure de répertoires, que cette structure soit rangée dans un système de fichiers sur un disque dur ou dans un fichier
archive
. Le format le plus courant de tels fichiers est le format ZIP, et Java a son propre format, JAR, qui signifie Java Archive. Ces deux formats sont ouverts par les logiciels de gestion d’archives courants, tels que WinZip ou WinRar.
Placer des dizaines de fichiers dans un seul, tout en conservant la bonne structure hiérarchique des répertoires, simplifie beaucoup les choses, notamment lors de la distribution d’une application. Notons que cette distribution peut se faire de façon classique, sous forme de diffusion de fichier, mais aussi de façon automatique au travers de l'Internet. Distribuer un code peut signifier autoriser son téléchargement au travers du net, en automatique. Ce qui commande le téléchargement peut très bien être un utilisateur, mais aussi une application distribuée. Enfin, cela permet de compresser les fichiers, et d’économiser de la place.
La dernière question qui reste en suspend pour la machine java est : où se trouve la structure hiérarchique des fichiers classe ? Quel est son point de montage dans le système de fichiers ? Où se trouvent les fichiers archive ?
La réponse est double. Tout d’abord, un certain nombres de fichiers archive sont connus par défaut, et se trouvent dans les répertoires d'installation de toute machine Java. C'est notamment le cas du fichier
rt.jar
(
rt
signifie runtime), qui contient la librairie standard.
Ensuite, la machine Java sait lire une variable d'environnement standard, appelée
CLASSPATH
, qui, comme son nom le suggère, contient un ensemble d’endroits où se trouvent des classes Java. Ces endroits peuvent être des répertoires, dans ce cas la machine Java va scanner cette arborescence à la recherche de fichiers
.class
, ou des fichiers portant l’extension
.zip
ou
.jar
.
Enfin, il est aussi possible de lancer la machine Java avec l’option
–classpath
pour lui indiquer d’autres endroits où aller chercher les classes.
3.3. Notion de classloader
Chaque classe est chargée dans la machine Java par un objet particulier, instance de la classe
ClassLoader
. On parle alors de
classloader
d'une classe. La classe
Class
, qui modélise les classes en Java, possède une méthode qui permet d'accèder au classloader qui l'a chargée. Il est donc toujours possible, lorsque l'on possède un objet, de savoir quel classloader a chargé la classe de cet objet, ou encore dans quel classloader il vit.
Les classloaders sont répartis dans une hiérarchie, au sommet de laquelle se trouve le
bootstrap classloader
. Ce classloader est le premier à être créé par la JVM. C'est ce classloader qui charge les classes de la hiérarchie
java.*
.
Un classloader est capable de créer autant de sous-classloader qu'il veut, qui vont à leur tour charger leurs propres classes, et pouvoir exécuter leur propre code. La règle est que lorsqu'un classloader a besoin d'une classe qu'il ne possède pas, il la demande à son classloader père, qui peut répéter le processus. Donc, deux classloader créés par un même père ne se connaissent pas, et ne peuvent pas échanger leurs classes. Il est donc possible d'avoir deux classes portant le même nom complet, chargées dans deux classloader différents, sans pour autant générer de conflit. En particulier, ces deux classes peuvent être différentes, cas que l'on rencontre souvent lorsque l'on utilise une même librairie open source dans des versions différentes.
Dans le fonctionnement standard de la JVM J2SE (par opposition à JEE), le bootstrap classloader crée un fils, l'extension classloader, qui prend en charge les classes se trouvant dans le répertoire
lib/ext
de l'installation standard du JRE.
Enfin, l'extension classloader crée le classloader qui nous intéresse : le system-classpath classloader. C'est lui qui charge les JAR et les classes spécifiés par la variable
CLASSPATH
de notre application.
Les classloaders fonctionnent sur deux principes en Java :
-
le principe de visibilité : un classloader enfant peut voir les classes chargées par son père. L'inverse n'est pas vrai.
-
le principe d'unicité : lorsqu'un père charge une classe, un fils ne la charge pas, il la demande au père.
Cette notion de classloader est au centre de la sécurité du code Java. Elle permet à la JVM de gérer de nombreuses applications différentes, sans que des conflits puissent éclater entre applications.
3.4. Bilan sur les classes chargées
Les classes chargées par la JVM sont donc :
-
celles qui se trouvent dans les répertoires de la distribution du JDK :
rt.jar
et
i18n.jar
par le bootstrap classloader, le contenu de
lib/ext
par l'extension classloader ;
-
les classes et JAR spécifiées par la variable d'environnement
CLASSPATH
, par la propriété système
java.class.path
et par l'option de lancement de la JVM
-cp
ou
-classpath
;
-
de plus, si l'un des JAR chargés possède un fichier
MANIFEST.MF
dans son répertoire
META-INF
, et que ce fichier définit un attribut
Class-Path
, alors les JAR précisés par cet attribut sont aussi chargés.
Les règles de visibilité des classes dépendent également de leur rangement dans les paquets. Revoyons les règles que nous avons déjà citées.
Les membres de classes peuvent être :
-
public
: toutes les autres classes y ont accès, quel que soit le paquet dans lequel elles se trouvent ;
-
protected
: toutes les classes de ce paquet y ont accès, ainsi que les classes des autres paquets qui héritent de cette classe ;
-
aucune directive : ce membre est accessible du paquet, et d’aucun autre endroit ;
-
private
: membre accessible uniquement de la classe même. Aucune autre classe ne peut voir ce membre.
Comme on peut le voir, la seule différence porte sur la directive
protected
, qui est un peu moins contraignante.
On notera que la directive
protected
a un sens différent du C++, puisqu’elle étend l’accès à ce membre à toutes les classes qui se trouvent dans le même paquet.
Enfin, si une classe n’est rangée dans aucun paquet (pas de déclaration
package
en première ligne, ce cas ne devrait jamais arriver !), elle est compilée dans un paquet anonyme, qui est le paquet par défaut. Les règles de visibilité s’appliquent alors, entre cette classe et les autres classes de ce paquet anonyme, et avec les autres classes rangées dans des paquets normaux. Il est fortement déconseillé de ne pas ranger ses classes dans un paquet.
3.6. Conseils d'écriture, bibliothèque standard
Il est très important de bien choisir sa structure de paquets lorsque l’on se lance dans le développement d’un projet important. Le nombre de classes et donc de fichiers augmente très rapidement, et plus ce nombre augmente, plus la structuration de l’ensemble doit être rigoureuse. On peut examiner sur quelques paquets principaux la façon dont la bibliothèque standard a été structurée, et s’en inspirer :
-
java.lang
: classes relatives au langage, on y trouve entre autres
String
,
Integer
,
Float
etc…
-
java.util
: classes utilitaires, telles que
Array
,
Collection
et ses dérivées,
Date
,
Calendar
;
-
java.sql
: classes du JDBC pour l’accès aux bases de données ;
-
java.text
: classes utilitaires pour formater les chaînes de caractères et les textes ;
-
java.io
: classes utilisées pour les entrées / sorties ;
-
java.awt
:
Abstract Windows Toolkit
, ce sont les classes de couche basse pour la gestion des interfaces graphiques ;
-
java.applet
: classes propres aux applets Java ;
-
java.net
: classes permettant l’accès aux ressources réseaux.
Dans tous les cas, les arborescences
java.*
et
javax.*
sont de bonnes sources d’inspiration pour structurer ses classes.