Come risolvere una collisione di versioni di artefatti in Maven

1. Panoramica

I progetti Maven multi-modulo possono avere grafici di dipendenza complessi. Questi possono avere risultati insoliti, più i moduli vengono importati l'uno dall'altro.

In questo tutorial, vedremo come risolvere la collisione di versione degli artefatti in Maven .

Inizieremo con un progetto multi-modulo in cui abbiamo deliberatamente utilizzato versioni diverse dello stesso artefatto. Quindi, vedremo come evitare di ottenere la versione sbagliata di un artefatto con l'esclusione o la gestione delle dipendenze.

Infine, proveremo a utilizzare il plugin m aven-enforcer per rendere le cose più facili da controllare, vietando l'uso di dipendenze transitive.

2. Collisione di versioni di artefatti

Ogni dipendenza che includiamo nel nostro progetto potrebbe collegarsi ad altri artefatti. Maven può importare automaticamente questi artefatti, chiamati anche dipendenze transitive. La collisione di versioni si verifica quando più dipendenze si collegano allo stesso artefatto, ma utilizzano versioni diverse.

Di conseguenza, potrebbero esserci errori nelle nostre applicazioni sia nella fase di compilazione che in fase di runtime .

2.1. Struttura del progetto

Definiamo una struttura di progetto multi-modulo con cui sperimentare. Il nostro progetto consiste in un genitore di collisione di versioni e tre moduli figli:

version-collision project-a project-b project-collision 

Il pom.xml per project-a e project-b sono quasi identici. L'unica differenza è la versione del manufatto com.google.guava da cui dipendono. In particolare, project-a utilizza la versione 22.0 :

  com.google.guava guava 22.0  

Ma project-b usa la versione più recente, 29.0-jre :

  com.google.guava guava 29.0-jre  

Il terzo modulo, progetto-collisione , dipende dagli altri due:

  com.baeldung project-a 0.0.1-SNAPSHOT   com.baeldung project-b 0.0.1-SNAPSHOT  

Quindi, quale versione di guava sarà disponibile per la collisione del progetto ?

2.2. Utilizzo delle funzionalità da una versione specifica delle dipendenze

Possiamo scoprire quale dipendenza viene utilizzata creando un semplice test nel modulo di collisione del progetto che utilizza il metodo Futures.immediateVoidFuture da guava :

@Test public void whenVersionCollisionDoesNotExist_thenShouldCompile() { assertThat(Futures.immediateVoidFuture(), notNullValue()); }

Questo metodo è disponibile solo dalla versione 29.0-jre . Lo abbiamo ereditato da uno degli altri moduli, ma possiamo compilare il nostro codice solo se abbiamo la dipendenza transitiva da project-b.

2.3. Errore di compilazione causato da un conflitto di versioni

A seconda dell'ordine delle dipendenze nel modulo di collisione del progetto , in alcune combinazioni Maven restituisce un errore di compilazione:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile (default-testCompile) on project project-collision: Compilation failure [ERROR] /tutorials/maven-all/version-collision/project-collision/src/test/java/com/baeldung/version/collision/VersionCollisionUnitTest.java:[12,27] cannot find symbol [ERROR] symbol: method immediateVoidFuture() [ERROR] location: class com.google.common.util.concurrent.Futures

Questo è il risultato della collisione della versione del manufatto com.google.guava . Per impostazione predefinita, per le dipendenze allo stesso livello in un albero delle dipendenze, Maven sceglie la prima libreria che trova. Nel nostro caso, entrambe le dipendenze di com.google.guava sono alla stessa altezza e viene scelta la versione precedente.

2.4. Utilizzo del plug-in di dipendenza Maven

Il plugin maven-dependency è uno strumento molto utile per presentare tutte le dipendenze e le loro versioni:

% mvn dependency:tree -Dverbose [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ project-collision --- [INFO] com.baeldung:project-collision:jar:0.0.1-SNAPSHOT [INFO] +- com.baeldung:project-a:jar:0.0.1-SNAPSHOT:compile [INFO] | \- com.google.guava:guava:jar:22.0:compile [INFO] \- com.baeldung:project-b:jar:0.0.1-SNAPSHOT:compile [INFO] \- (com.google.guava:guava:jar:29.0-jre:compile - omitted for conflict with 22.0)

Il flag -Dverbose mostra artefatti in conflitto. In effetti, abbiamo una dipendenza com.google.guava in due versioni: 22.0 e 29.0-jre. Quest'ultimo è quello che vorremmo usare nel modulo di collisione del progetto .

3. Esclusione di una dipendenza transitiva da un artefatto

Un modo per risolvere un conflitto di versioni è rimuovere una dipendenza transitiva in conflitto da artefatti specifici . Nel nostro esempio, non vogliamo che la libreria com.google.guava venga aggiunta in modo transitorio dal progetto-a artefatto.

Pertanto, possiamo escluderlo nel pom di collisione progetto :

  com.baeldung project-a 0.0.1-SNAPSHOT   com.google.guava guava     com.baeldung project-b 0.0.1-SNAPSHOT  

Ora, quando eseguiamo il comando dependency: tree , possiamo vedere che non è più lì:

% mvn dependency:tree -Dverbose [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ project-collision --- [INFO] com.baeldung:project-collision:jar:0.0.1-SNAPSHOT [INFO] \- com.baeldung:project-b:jar:0.0.1-SNAPSHOT:compile [INFO] \- com.google.guava:guava:jar:29.0-jre:compile

Di conseguenza, la fase di compilazione termina senza errori e possiamo utilizzare le classi ei metodi dalla versione 29.0-jre .

4. Utilizzo della sezione Gestione delle dipendenze

La sezione dependencyManagement di Maven è un meccanismo per centralizzare le informazioni sulle dipendenze . Una delle sue caratteristiche più utili è controllare le versioni degli artefatti usati come dipendenze transitive.

Con questo in mente, creiamo una configurazione di gestione delle dipendenze nel nostro genitore pom :

   com.google.guava guava 29.0-jre   

Di conseguenza, Maven si assicurerà di utilizzare la versione 29.0-jre di com.google.guava artefatto in tutti i moduli figlio:

% mvn dependency:tree -Dverbose [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ project-collision --- [INFO] com.baeldung:project-collision:jar:0.0.1-SNAPSHOT [INFO] +- com.baeldung:project-a:jar:0.0.1-SNAPSHOT:compile [INFO] | \- com.google.guava:guava:jar:29.0-jre:compile (version managed from 22.0) [INFO] \- com.baeldung:project-b:jar:0.0.1-SNAPSHOT:compile [INFO] \- (com.google.guava:guava:jar:29.0-jre:compile - version managed from 22.0; omitted for duplicate)

5. Prevenire le dipendenze transitive accidentali

The maven-enforcer-plugin provides many built-in rules that simplify the management of a multi-module project. One of them bans the use of classes and methods from transitive dependencies.

Explicit dependency declaration removes the possibility of version collision of artifacts. Let's add the maven-enforcer-plugin with that rule to our parent pom:

 org.apache.maven.plugins maven-enforcer-plugin 3.0.0-M3   enforce-banned-dependencies  enforce         

As a consequence, we must now explicitly declare the com.google.guava artifact in our project-collision module if we want to use it ourselves. We must either specify the version to use, or set up dependencyManagement in the parent pom.xml. This makes our project more mistake proof, but requires us to be more explicit in our pom.xml files.

6. Conclusion

In this article, we've seen how to resolve a version collision of artifacts in Maven.

First, we explored an example of a version collision in a multi-module project.

Quindi, abbiamo mostrato come escludere le dipendenze transitive nel pom.xml . Abbiamo esaminato come controllare le versioni delle dipendenze con la sezione dependencyManagement nel genitore pom.xml .

Infine, abbiamo provato il plugin maven-enforcer per vietare l'uso di dipendenze transitive per costringere ogni modulo a prendere il controllo da solo.

Come sempre, il codice mostrato in questo articolo è disponibile su GitHub.