| Ce site met a disposition le build journalier de la traduction francaise du Maven: The Definitive Guide Consultez : | ![]() |
Sur des projets plus importants, des dépendances ont souvent tendance à se glisser dans un POM au fur et à mesure que leur nombre augmente. Lorsque les dépendances changent, vous vous retrouvez avec des dépendances inutilisées ou avec certaines des bibliothèques nécessaires qui n'ont pas été déclarées explicitement. Comme Maven 2.x inclut les dépendances transivites du scope compile, votre projet peut compiler correctement mais ne pas fonctionner en production. Prenons par exemple le cas d'un projet qui utilise les classes d'une librairie très utilisée comme Jakarta Commons BeanUtils. Au lieu de déclarer une dépendance explicite sur BeanUtils, votre projet dépend d'un projet comme Hibernate qui référence BeanUtils comme dépendance transitive. Votre projet peut donc compiler et s'exécuter correctement. Mais si vous mettez à jour votre version d'Hibernate et que cette nouvelle version ne dépend plus de BeanUtils, vous allez commencer à rencontrer des erreurs de compilation et d'exécution, et il ne sera pas évident de trouver pourquoi votre projet a soudainement arrêté de compiler. De plus, comme vous n'avez pas explicitement défini de version pour cette dépendance, Maven ne peut résoudre correctement un éventuel conflit de version.
Une bonne pratique avec Maven est de toujours déclarer explicitement les dépendances pour les classes auxquelles vous faites référence dans votre code. Si vous importez des classes de Commons BeanUtils, vous devriez aussitôt déclarer une dépendance directe sur Commons BeanUtils. Heureusement, grâce à l'analyse du bytecode, le plugin Maven Dependency peut vous aider à découvrir les références directes à des dépendances. Prenons les POMs que nous venons d'optimiser et regardons si on peut y trouver des erreurs :
$ mvn dependency:analyze
[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO] Chapter 8 Simple Parent Project
[INFO] Chapter 8 Simple Object Model
[INFO] Chapter 8 Simple Weather API
[INFO] Chapter 8 Simple Persistence API
[INFO] Chapter 8 Simple Command Line Tool
[INFO] Chapter 8 Simple Web Application
[INFO] Chapter 8 Parent Project
[INFO] Searching repository for plugin with prefix: 'dependency'.
...
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 8 Simple Object Model
[INFO] task-segment: [dependency:analyze]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing dependency:analyze
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [dependency:analyze]
[WARNING] Used undeclared dependencies found:
[WARNING] javax.persistence:persistence-api:jar:1.0:compile
[WARNING] Unused declared dependencies found:
[WARNING] org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile
[WARNING] org.hibernate:hibernate:jar:3.2.5.ga:compile
[WARNING] junit:junit:jar:3.8.1:test
...
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 8 Simple Web Application
[INFO] task-segment: [dependency:analyze]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing dependency:analyze
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] No sources to compile
[INFO] [dependency:analyze]
[WARNING] Used undeclared dependencies found:
[WARNING] org.sonatype.mavenbook.optimize:simple-model:jar:1.0:compile
[WARNING] Unused declared dependencies found:
[WARNING] org.apache.velocity:velocity:jar:1.5:compile
[WARNING] javax.servlet:jstl:jar:1.1.2:compile
[WARNING] taglibs:standard:jar:1.1.2:compile
[WARNING] junit:junit:jar:3.8.1:test
Dans ces traces tronquées vous pouvez voir le résultat du goal dependency:analyze. Ce goal analyse le projet pour voir
s'il existe des dépendances indirectes, ou si on fait référence à des dépendances qui ne sont pas déclarées
directement. Dans le projet simple-model, le plugin Dependency indique une “dépendance utilisée
mais pas déclarée” sur javax.persistence:persistence-api. Pour obtenir plus
d'informations, allez dans le répertoire simple-model et exécutez le goal
dependency:tree qui va lister toutes les dépendances du projet, directes et
transitives.
$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 8 Simple Object Model
[INFO] task-segment: [dependency:tree]
[INFO] ------------------------------------------------------------------------
[INFO] [dependency:tree]
[INFO] org.sonatype.mavenbook.optimize:simple-model:jar:1.0
[INFO] +- org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile
[INFO] | \- javax.persistence:persistence-api:jar:1.0:compile
[INFO] +- org.hibernate:hibernate:jar:3.2.5.ga:compile
[INFO] | +- net.sf.ehcache:ehcache:jar:1.2.3:compile
[INFO] | +- commons-logging:commons-logging:jar:1.0.4:compile
[INFO] | +- asm:asm-attrs:jar:1.5.3:compile
[INFO] | +- dom4j:dom4j:jar:1.6.1:compile
[INFO] | +- antlr:antlr:jar:2.7.6:compile
[INFO] | +- cglib:cglib:jar:2.1_3:compile
[INFO] | +- asm:asm:jar:1.5.3:compile
[INFO] | \- commons-collections:commons-collections:jar:2.1.1:compile
[INFO] \- junit:junit:jar:3.8.1:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Dans cette trace, nous pouvons voir que la dépendance persistence-api est apportée par
hibernate. Une analyse rapide du code source de ce module va trouver de nombreux imports de
javax.persistence ce qui confirme que nous faisons directement référence à
cette dépendance. La correction la plus simple est d'ajouter une référence directe à cette dépendance. Dans cet
exemple, nous indiquons la version de la dépendance dans la section dependencyManagement de
simple-parent car cette dépendance est liée à Hibernate et c'est là qu'on déclare la version
d'Hibernate. Un jour va arriver où vous allez vouloir changer la version d'Hibernate pour votre projet. Mettre la
version de persistence-api à côté de la version d'Hibernate permettra d'éviter d'oublier de la
mettre à jour lorsque vous éditerez le POM parent pour modifier la version d'Hibernate.
Si vous regardez le résultat du dependency:analyze pour le module
simple-web, vous verrez qu'il faut aussi ajouter une référence directe à
simple-model. Le code de simple-webapp fait directement référence aux objets
métier de simple-model, et simple-model est visible dans
simple-webapp en tant que dépendance transitive via simple-persist. Puisqu'il
s'agit d'une dépendance vers un module frère qui partage la même version et le même
groupId, la dépendance peut être déclarée dans le pom.xml de
simple-webapp avec ${project.groupId} et ${project.version}.
Comment fait le plugin Maven Dependency pour trouver ces problèmes ? Comment dependency:analyze sait-il à quelles classes
et à quelles dépendances le bytecode de votre projet fait référence ? Le plugin Dependency utilise l'outil ASM
d'ObjectWeb (http://asm.objectweb.org/) pour analyser le bytecode brut. Le plugin Dependency utilise ASM pour parcourir toutes les classes du
projet, et lister toutes les classes référencées qui n'en font pas partie. Ensuite, il parcourt toutes les
dépendances directes et transitives, et marque les classes trouvées dans les dépendances directes. Toute classe qui
n'a pas été trouvée dans les dépendances directes, est recherchée dans les dépendances transitives et ainsi il
génère la liste des “dépendances utilisées, mais non déclarées”.
Par contre, la liste des dépendances déclarées mais non utilisées est un peu plus délicate à valider et bien moins utile que les “dépendances utilisées mais non déclarées”.
Premièrement, car certaines dépendances ne sont utilisées qu'à l'exécution ou que pour les tests, et donc ne seront
pas référencées dans le bytecode. Celles-ci sont assez faciles à voir dans les traces ; par exemple, JUnit apparaît
dans cette liste comme on pouvait s'y attendre car elle n'est utilisée que pour les tests unitaires. Vous
remarquerez aussi les dépendances vers Velocity et l'API Servlet pour le module simple-web. Là
encore, on pouvait s'y attendre, car le projet n'a aucune référence directe aux classes de ces artefacts, même s'ils
restent essentiels durant l'exécution.
Soyez prudents lorsque vous supprimez une dépendance déclarée mais inutilisée, à moins que vous n'ayez une très bonne couverture de tests, vous risquez d'avoir des surprises pendant l'exécution. Un problème beaucoup plus dangereux peut apparaître avec l'optimisation du bytecode. Par exemple, le compilateur a le droit de substituer la valeur d'une constante et d'optimiser en supprimant la référence. Supprimer cette dépendance va empêcher la compilation, alors que l'outil indique qu'elle n'est pas utilisée. Les versions futures du plugin Maven Dependency fourniront de meilleures techniques pour détecter ou ignorer ces problèmes.
Vous devriez utiliser dependency:analyze régulièrement pour détecter ces
erreurs classiques dans vos projets. On peut configurer le build pour qu'il échoue si ce plugin rencontre certaines
conditions, et il peut aussi produire un
rapport.