• 20 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 5/16/23

Gérez efficacement les dépendances

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 :

<project>
    ...
    <!-- =============================================================== -->
    <!-- Dépendances -->
    <!-- =============================================================== -->
    <dependencies>
        <!-- ===== Modules ===== -->
        ...

        <!-- ===== Bibliothèques tierces ===== -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.1</version>
        </dependency>
    </dependencies>
    ...
</project>

Reprenons le cas du projet de gestion de ticket initié dans le chapitre précédent :

  1. Ajoutez cette dépendance dans le module ticket-webapp.

  2. Relancez un packaging du projet ticket (avec la commande mvn package).

  3. 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 !

  1. Ajoutez simplement la dépendance vers org.apache.commons:commons-text:1.1 :

    <project>
        ...
        <dependencies>
            ...
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-text</artifactId>
                <version>1.1</version>
            </dependency>
        </dependencies>
        ...
    </project>
  2. Relancez un packaging du projet ticket (avec la commande mvn package).

  3. Ouvrez le fichier WAR généré (ticket-webapp/target/ticket-webapp.war) : vous voyez que les bibliothèques commons-text et commons-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 :

<project>
    ...
    <dependencies>
        ...
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.1</version>
            <exclusions>
                <!-- La dépendance vers commons-lang3 est exclue -->
                <exclusion>
                    <groupId>org.apache.commons</groupId>
                    <artifactId>commons-lang3</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    ...
</project>

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 servlet

  • javax.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)

<project>
    ...
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.bval</groupId>
            <artifactId>bval-jsr</artifactId>
            <version>1.1.2</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    ...
</project>

Voici l'explication sur les scopes utilisés :

Dépendance

Scope

Explication

junit

test

Cette bibliothèque de test unitaire n'est utile que pour la phase de test

servlet-api

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.

validation-api

compile

J'ai besoin de cette bibliothèque lors de la compilation pour utiliser les annotations de cette API dans mes Beans

bval-jsr

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 org.hibernate:hibernate-validator)

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 :

  1. Dans le POM du projet parent, ajoutez :

    <project>
        ...
        <dependencyManagement>
            <dependencies>
                ...
                <dependency>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                    <version>4.12</version>
                    <scope>test</scope>
                </dependency>
                <dependency>
                    <groupId>javax.validation</groupId>
                    <artifactId>validation-api</artifactId>
                    <version>1.1.0.Final</version>
                </dependency>
                <dependency>
                    <groupId>org.apache.bval</groupId>
                    <artifactId>bval-jsr</artifactId>
                    <version>1.1.2</version>
                    <scope>runtime</scope>
                </dependency>
                <dependency>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                    <version>2.5</version>
                    <scope>provided</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        ...
    </project>
  2. Dans le module ticket-webapp, conservez uniquement ceci :

    <project>
        ...
        <dependencies>
            ...
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </dependency>
            <dependency>
                <groupId>javax.validation</groupId>
                <artifactId>validation-api</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.bval</groupId>
                <artifactId>bval-jsr</artifactId>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
            </dependency>
        </dependencies>
        ...
    </project>

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 :

<project>
    ...
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-framework-bom</artifactId>
                <version>4.3.11.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
        ...
    </dependencyManagement>
    ...
</project>

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.

Example of certificate of achievement
Example of certificate of achievement