web-dev-qa-db-fra.com

Version de bibliothèque en conflit dans un projet maven Java

Lors de la construction d'un projet maven qui a de nombreuses dépendances, certaines de ces dépendances dépendent de la même bibliothèque mais utilisent une version différente qui provoque des erreurs lors de l'exécution d'une application.

Par exemple, si j'ajoute deux dépendances de projet différentes, A et B qui dépendent toutes deux du client http Apache commons mais chacune sur une version différente, une fois que le chargeur de classe charge les classes client Apache commons http de A, B essaiera de les utiliser depuis ils sont déjà chargés par le chargeur de classe.

Mais le bytecode de B dépend d'une version différente des classes chargées provoquant plusieurs problèmes lors de l'exécution de l'application. Une exception courante est l'exception de méthode introuvable (puisque la version A du client http n'utilise plus de méthode spécifique).

Quelle est la stratégie générale lors de la construction pour éviter de tels conflits? Doit-on vérifier manuellement l'arborescence des dépendances pour déterminer quelles bibliothèques communes entrent en collision les unes avec les autres?

41
John Papastergiou

Bienvenue dans enfer de la dépendance maven , comme on l'appelle affectueusement. Il s'agit d'un problème quelque peu courant à mesure que les projets se développent et que davantage de dépendances externes sont introduites.

Outre Apache Commons (mentionné dans votre question d'origine), les cadres de journalisation (log4j, slf4j) sont un autre coupable fréquent.

Je suis d'accord avec les conseils donnés par "matts" sur la façon de résoudre les conflits une fois qu'ils sont identifiés. En termes de détection précoce de ces conflits de version, vous pouvez également utiliser le plugin maven "forcer ". Reportez-vous à la configuration "dependencyConvergence" . Voir aussi this SO post .

L'utilisation du plugin exécuteur échouera immédiatement à la génération en cas de conflit de version, ce qui vous évitera les vérifications manuelles. Il s'agit d'une stratégie agressive, mais empêche le type de problèmes d'exécution qui ont incité votre question/publication. Comme n'importe quoi, le plugin d'exécuteur a des avantages et des inconvénients. Nous avons commencé à l'utiliser au cours de la dernière année, mais nous avons découvert que cela pouvait être une bénédiction et une malédiction. De nombreuses versions des bibliothèques/frameworks sont rétrocompatibles, et donc dépendre (directement ou indirectement) des versions 1.2.3 et 1.2.4 est souvent correct à la fois à la compilation et à l'exécution. Cependant, le plugin exécuteur signalera ce conflit et vous demandera de déclarer exactement la version que vous souhaitez. En supposant que le nombre de conflits de dépendance est faible, cela ne nécessite pas beaucoup de travail. Cependant, une fois que vous introduisez un grand framework (par exemple Spring MVC), il peut devenir désagréable.

J'espère que ce sont des informations utiles.

26
Todd Patterson

Vous pouvez utiliser le tree goal du plugin de dépendances Maven pour afficher toutes les dépendances transitives dans votre projet et rechercher les dépendances qui disent "omis pour conflit" . 1

mvn dependency:tree -Dverbose
mvn dependency:tree -Dverbose | grep 'omitted for conflict'

Une fois que vous savez quelle dépendance a des conflits de version, vous pouvez utiliser le paramètre includes pour afficher uniquement les dépendances qui mènent à celle-ci pour voir comment une dépendance particulière est récupérée. Par exemple, un projet dans lequel différentes versions de C sont attirés par A et B:

mvn dependency:tree -Dverbose -Dincludes=project-c

[INFO] com.my-company:my-project:jar:1.0-SNAPSHOT
[INFO] +- project-a:project-a:jar:0.1:compile
[INFO] |  \- project-c:project-c:jar:1.0:compile
[INFO] \- project-b:project-b:jar:0.2:compile
[INFO]    \- project-x:project-x:jar:0.1:compile
[INFO]       \- (project-c:project-c:jar:2.0:compile - omitted for conflict)

Pour résoudre réellement le conflit, dans certains cas, il peut être possible de trouver une version de la dépendance transitive avec laquelle vos deux dépendances principales fonctionneront. Ajoutez la dépendance transitive à la section dependencyManagement de votre pom et essayez de changer la version jusqu'à ce que cela fonctionne.

Cependant, dans d'autres cas, il peut ne pas être possible de trouver une version de la dépendance qui fonctionne pour tout le monde. Dans ces cas, vous devrez peut-être reculer la version sur l'une des dépendances principales afin de lui faire utiliser une version de la dépendance transitive qui fonctionne pour tout le monde. Par exemple, dans l'exemple ci-dessus, A 0,1 utilise C 1,0 et B 0,2 utilise C 2,0. Supposons que C 1.0 et 2.0 sont complètement incompatibles. Mais il est peut-être possible que votre projet utilise B 0.1 à la place, ce qui dépend de C 1.5, qui est compatible avec C 1.0.

Bien sûr, ces deux stratégies ne fonctionneront pas toujours, mais j'ai déjà réussi avec elles. D'autres options plus drastiques incluent l'empaquetage de votre propre version de la dépendance qui corrige l'incompatibilité ou la tentative d'isoler les deux dépendances dans des chargeurs de classe distincts.

37
matts

Vous pouvez utiliser le plugin maven -forcer dans votre pom pour forcer des versions spécifiques des dépendances transitives. Cela vous aiderait à éviter les omissions de la configuration de pom en cas de conflit.

C'est ce qui a fonctionné pour moi, et j'ai pu changer les versions pour qu'elles correspondent. Si vous ne pouvez pas changer de version, cela ne sera pas très utile.

Convergence des dépendances

<project>
...
  <build>
    <plugins>
      ...
      <plugin>
        <groupId>org.Apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>1.4</version>
        <executions>
          <execution>
            <id>enforce</id>
            <configuration>
              <rules>
                <dependencyConvergence/>
              </rules>
            </configuration>
            <goals>
              <goal>enforce</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      ...
    </plugins>
  </build>
  ...
</project>

Forcer une version sur la dépendance à l'aide de crochets:

<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <scope>compile</scope>
        <version>[1.0.0]</version>
</dependency>
2
Gaʀʀʏ

Je voudrais étendre les réponses de Todd et Matts au fait que vous pouvez:

  • mvn dependency:tree -Dverbose -Dincludes=project-c

  • Ajoutez un <exclusions/> tag pour toutes vos dépendances qui ont une dépendance transitive de project-c.

  • Ou bien, dans votre projet, définissez explicitement project-c comme dépendance afin de passer outre les transitifs et d'éviter les conflits. (Cela apparaîtra toujours dans votre arborescence lorsque vous utilisez `-Dverbose).

Alternativement, si ces projets sont sous votre contrôle, vous pouvez simplement mettre à niveau la version de project-c.

1
carlspring