Une des forces de Maven est sa puissance dans la gestion des dépendances. En effet, grâce à Maven, vous pouvez facilement ajouter des bibliothèques tierces à votre application.
Ajouter une dépendance
Comme nous l'avons vu dans le chapitre précédent, pour ajouter une dépendance à votre projet, il suffit d'ajouter une balise <dependency>
dans la section <dependencies>
.
Prenons un exemple. Dans votre application, vous manipulez des collections et vous aimeriez vous appuyer sur la bibliothèque Apache Commons Collections™.
Voici comment ajouter une dépendance vers org.apache.commons:commons-collections4:4.1
:
...
<!-- =============================================================== -->
<!-- Dépendances -->
<!-- =============================================================== -->
<!-- ===== Modules ===== -->
...
<!-- ===== Bibliothèques tierces ===== -->
org.apache.commons
commons-collections4
4.1
...
Reprenons le cas du projet de gestion de ticket initié dans le chapitre précédent :
Ajoutez cette dépendance dans le module
ticket-webapp
.Relancez un packaging du projet
ticket
(avec la commandemvn package
).Ouvrez le fichier WAR généré (
ticket-webapp/target/ticket-webapp.war
). Vous voyez que la dépendance a été intégrée :🗄 ticket-webapp.war ├── ... └── 🗁 WEB-INF └── 🗁 lib ├── 🗎 commons-collections4-4.1.jar └── ...
Où trouver des dépendances ?
Mais comment fait Maven pour récupérer les dépendances que j'ajoute à mon projet ?
Eh bien pour cela, Maven utilise des repositories. Un repository est un endroit où sont stockées des dépendances sous un format imposé par Maven.
Par défaut, Maven utilise le repository central. Celui-ci se trouve à l'adresse : http://repo1.maven.apache.org/maven2/
Quand vous ajoutez une dépendance à votre projet, Maven la télécharge depuis ce repository et la stocke dans votre repository local. Vous vous souvenez ? C'est le chemin que nous avions configuré dans le fichier settings.xml
dans le premier chapitre de ce cours.
La transitivité des dépendances
Maven gère également la transitivité des dépendances. Cela signifie que Maven est capable d'ajouter automatiquement les dépendances requises par les dépendances que vous avez ajoutées.
Dans votre application web (module ticket-webapp
), vous aimeriez utiliser sur la bibliothèque Apache Commons Text™. Cette dernière s'appuie elle-même sur la bibliothèque Apache Commons Lang™.
Vous n'avez pas à gérer cela vous-même, Maven va le faire pour vous !
Ajoutez simplement la dépendance vers
org.apache.commons:commons-text:1.1
:... ... org.apache.commons commons-text 1.1 ...
Relancez un packaging du projet
ticket
(avec la commandemvn package
).Ouvrez le fichier WAR généré (
ticket-webapp/target/ticket-webapp.war
) : vous voyez que les bibliothèquescommons-text
etcommons-lang
ont été intégrées :🗄 ticket-webapp.war ├── ... └── 🗁 WEB-INF └── 🗁 lib ├── 🗎 commons-lang-3.5.jar ├── 🗎 commons-text-1.1.jar └── ...
Ignorer des sous-dépendances
Nous avons pour le moment vu des cas assez simples, mais dans certains cas, la transitivité peut vite faire gonfler le nombre de dépendances car elle ajoute les dépendances en cascade.
Si on a des dépendances telles que A → B → C → D, alors A dépendra de B, C et D. Si A n'utilise qu'une petite partie de B qui n'a pas besoin de C, alors il est possible d'indiquer à Maven d'ignorer la dépendance B → C dans A. A ne dépendra alors que de B et plus de C et D.
Reprenons l'exemple de commons-text
. Imaginez que dans votre module ticket-webapp
, vous n'utilisiez que du code de commons-text
qui n'a pas besoin de commons-lang
. Vous allez pouvoir exclure la dépendance vers commons-lang
de la chaîne de transitivité de commons-text
dans ticket-webapp
.
Ajoutez pour cela une section <exclusions>
dans la déclaration de la dépendance vers commons-text
dans ticket-webapp
:
...
...
org.apache.commons
commons-text
1.1
<!-- La dépendance vers commons-lang3 est exclue -->
org.apache.commons
commons-lang3
...
Si vous relancez un packaging du projet, vous verrez que la bibliothèque commons-text
est intégrée au WAR généré mais pas la bibliothèque commons-lang
.
Définir la portée d'une dépendance
Il se peut que la dépendance que vous voulez ajouter ne soit liée qu'à un contexte particulier comme l'exécution des tests par exemple. C'est le cas de la dépendance vers JUnit ajoutée par l'archétype quickstart. En effet, il s'agit d'un framework de test unitaire et cette bibliothèque n'a pas à être déployée avec votre application en production.
Pour indiquer cela à Maven, les dépendances sont attachées à un scope.
Par défaut ce scope est compile. Ce scope indique que la dépendance est utilisée lors de la compilation et sera accessible dans tous les contextes.
Il existe d'autres scopes. Si je reprends la dépendance vers JUnit, vous remarquez qu'elle est attachée au scope test. Cela veut dire qu'elle n'est accessible que lors de la compilation des tests et leur exécution.
Il existe aussi des scopes comme provided ou runtime. provided indique que la dépendance est disponible à la compilation, mais elle devra être fournie par le contexte d'exécution (le serveur d'application par exemple). Avec runtime en revanche, la dépendance n'est pas accessible lors de la compilation, mais elle est disponible à l'exécution.
Voici un tableau récapitulatif des scopes, de leur transitivité et des contextes dans lesquels la dépendance correspondante est accessible :
Scope | Transitif* | Compilation | Test | Exécution |
---|---|---|---|---|
compile | ✓ | ✓ | ✓ | ✓ |
test |
|
| ✓ |
|
provided |
| ✓ | ✓ |
|
runtime | ✓ |
| ✓ | ✓ |
* Si la colonne n'est pas cochée (✓), cela signifie que la transitivité en tant que sous-dépendance s'arrête là. Je reformule : lorsque vous ajoutez à votre projet une dépendance vers X et que X a une dépendance vers Y, si le scope de la dépendance vers Y dans X est :
transitif (compile ou runtime), Maven ajoute la dépendance vers Y à votre projet
non transitif (test ou provided), Maven n'ajoute pas la dépendance vers Y à votre projet
De manière plus précise, si votre projet a une dépendance vers X et X une dépendance vers Y, voici le scope qui sera donné par Maven à Y dans votre projet en fonction :
du scope de X dans votre projet (colonne de gauche)
du scope de Y dans X (ligne d'entête)
Scope : X↓ ❘ Y→ | compile | test | provided | runtime |
---|---|---|---|---|
compile | compile | ∅ | ∅ | runtime |
test | test | ∅ | ∅ | test |
provided | provided | ∅ | ∅ | provided |
runtime | runtime | ∅ | ∅ | runtime |
Utilisation des différents scopes
Je vais prendre des exemples pour illustrer l'utilisation des scopes dans le module ticket-webapp
.
Je vais ajouter les dépendances suivantes :
junit:junit
pour faire des tests unitaires.javax.servlet:servlet-api
pour pouvoir implémenter une servletjavax.validation:validation-api
pour pouvoir utiliser l'API de validation de Bean (JSR 303)org.apache.bval:bval-jsr
comme implémentation de l'API de validation de Bean (JSR 303)
...
junit
junit
4.12
test
javax.servlet
servlet-api
2.5
provided
javax.validation
validation-api
1.1.0.Final
compile
org.apache.bval
bval-jsr
1.1.2
runtime
...
Voici l'explication sur les scopes utilisés :
Dépendance | Scope | Explication |
---|---|---|
| test | Cette bibliothèque de test unitaire n'est utile que pour la phase de test |
| provided | J'ai besoin de cette bibliothèque lors de la compilation pour créer une Servlet. Cependant, je ne dois pas l'avoir dans les WAR générés car elle entrerait en conflit avec celle fournie par le serveur d'application Java EE lors de l'éxécution. |
| compile | J'ai besoin de cette bibliothèque lors de la compilation pour utiliser les annotations de cette API dans mes Beans |
| runtime | Cette bibliothèque est une implémentation de l'API de validation de Bean (JSR 303). Mon code ne l'utilise pas directement, donc elle n'est pas nécessaire à la compilation. Cependant, à l'éxécution de l'application, une implémentation de l'API est nécessaire afin de procéder à la validation des Beans. Cette bibliothèque est une des implémentations possibles (il y a aussi |
Après un mvn clean package
, voici donc les bibliothèques contenues dans le WAR :
🗄 ticket-webapp.war
├── ...
└── 🗁 WEB-INF
└── 🗁 lib
├── 🗎 bval-core-1.1.2.jar (sous-dépendance de bval-jsr)
├── 🗎 bval-jsr-1.1.2.jar
├── 🗎 commons-lang3-3.5.jar (sous-dépendance de bval-jsr)
└── 🗎 validation-api-1.1.0.Final.jar
Gérer les dépendances de manière globale
Vous vous souvenez sûrement que nous avions vu dans le chapitre précédent qu'il est possible de gérer vos dépendances de manière globale dans votre projet. Cela se fait grâce à la section <dependencyManagement>
dans le POM du projet parent.
Vous pouvez ainsi lister toutes les dépendances utilisables dans les modules dans le projet parent, fixer leurs versions, gérer les exclusions...
Il ne vous reste plus qu'à ajouter simplement les dépendances dans vos modules sans (presque) vous soucier du reste.
Vous centralisez et homogénéisez ainsi la gestion des dépendances dans votre projet.
Mettons cela en pratique dans le projet ticket :
Dans le POM du projet parent, ajoutez :
... ... junit junit 4.12 test javax.validation validation-api 1.1.0.Final org.apache.bval bval-jsr 1.1.2 runtime javax.servlet servlet-api 2.5 provided ...
Dans le module ticket-webapp, conservez uniquement ceci :
... ... junit junit javax.validation validation-api org.apache.bval bval-jsr javax.servlet servlet-api ...
Utiliser une « Bill Of Materials »
Il est courant qu'un framework soit composé de plusieurs modules (ex. : Spring®, Apache Struts™...). Dans ce cas, au lieu de définir, une par une, toutes les dépendances vers ces modules, il est possible d'utiliser, si elle existe, une « Bill Of Materials» (BOM). Il s'agit d'un fichier POM spécifique, mis à disposition par les mainteneurs du framework. Il contient la définition des dépendances fournies par celui-ci.
Pour l'utiliser, il suffit d'importer le POM de cette « Bill Of Materials » dans la section dependencyManagement de votre projet avec le type pom
et le scope import
.
Voici un exemple avec la BOM de Spring :
...
org.springframework
spring-framework-bom
4.3.11.RELEASE
pom
import
...
...
Versions Snapshot et Release
Il y a un concept fort dans Maven qui s'appelle la reproductibilité de la construction. Cela veut dire que si vous ne changez rien et relancez un packaging de votre projet, vous devez obtenir le même livrable (à quelques deltas près comme les dates de construction...).
Ceci concerne également les dépendances que vous avez ajoutées à votre projet. Pour garantir au maximum cette reproductibilité avec les dépendances, il y a un principe de base : une version d'une dépendance est conventionnellement immuable. Cela veut dire qu'une fois la version publiée, elle ne sera plus modifiée. Si un changement doit intervenir dans le code, une nouvelle version sera publiée.
Ces versions s'appellent des releases.
Cependant, il arrive que vous deviez dépendre d'un élément en cours de développement. Dans ce cas, la version de cette élément sera une version snapshot. Cela est précisé dans Maven avec le suffixe -SNAPSHOT
à la fin de la version.
Une version snapshot peut être modifiée à tout moment et elle fait donc l'objet d'un traitement particulier dans Maven.
Résumons
Résumons ce que nous venons de voir dans ce chapitre.
Les dépendances s'ajoutent grâce à la balise <dependency>
.
Elles sont rattachées à un scope qui par défaut est compile, mais vous pouvez en spécifier d'autres comme runtime ou test par exemple.
Suivant leur scope, Maven ajoute automatiquement les sous-dépendances : c'est la transitivité des dépendances. Cependant, vous pouvez exclure des sous-dépendances avec la balise <exclusions>
.
Voici un tableau récapitulatif des scopes, de leur transitivité et des contextes dans lesquels la dépendance correspondante est accessible :
Scope | Transitif | Compilation | Test | Exécution |
---|---|---|---|---|
compile | ✓ | ✓ | ✓ | ✓ |
test |
|
| ✓ |
|
provided |
| ✓ | ✓ |
|
runtime | ✓ |
| ✓ | ✓ |
Les versions de type release sont immuables, contrairement aux versions de type snapshot (version avec le suffixe -SNAPSHOT
).
Enfin, je vous conseille de gérer les dépendances globalement au niveau du projet parent pour les projets multi-modules. Cela se fait grâce à la section <dependencyManagement>
. Si elle est disponible, vous pouvez importer dans cette section la « Bill Of Materials » des frameworks.
Si vous voulez plus de détails sur la gestion des dépendances vous pouvez consulter la documentation :
Conclusion
Nous arrivons à présent à la fin de cette première partie du cours. Vous savez maintenant comment créer un nouveau projet Maven mais aussi comment bien l'organiser et gérer ses dépendances. Dans la partie suivante, je vais vous apprendre à automatiser et personnaliser la construction de votre projet.