Ce site met a disposition le build journalier de la traduction francaise du Maven: The Definitive Guide
Consultez :
  • Les documents de reference sur le projet original
  • Les sources de la traduction fr sur GitHub
  • maven


    8.5. Optimisation avec le plugin Maven Dependency

    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.