Maven依賴傳遞和衝突解決

 

好機會,我要幫女同事解決Maven衝突問題
https://mp.weixin.qq.com/s/swqB37wSv4kdYcHSQwbsHg

 

好機會,我要幫女同事解決Maven衝突問題


 

任何一個故事起因最重要

任何一個職業,女生都有絕對的優勢。更別提 IT 行業了,在部門中要是有女程序猿那肯定是香餑餑,備受呵護呀。

之前有一次,一位剛來的妹子遇到問題了,畫風頓時就變成上面的圖片了,羣起而圍之,但是最後的結果並不理想,還是得我出馬(此處有點小吹牛)。

妹子遇到的是 Jar 包衝突的問題,錯誤信息是 Caused by: java.lang.ClassNotFoundException,看錯誤要麼就是缺少某個 Jar 包,要麼就是衝突了。

其實在工作中經常會遇到這種衝突的問題,比如:Caused by:java.lang.NoSuchMethodError 這個異常信息也是衝突導致的,想要解決衝突問題就必須得知道哪裏衝突了(好像是廢話)。

大部分都是用 Maven 來管理依賴的 Jar,今天這篇文章主要是講解如何解決 Maven 帶來的依賴衝突問題。

 

Maven 回顧

Maven 自述

Maven 是用於構建和管理 Java 項目的工具。對於 Java 方向的來說,Maven 幾乎都要接觸和使用。當然也有其他的工具來代替 Maven,比如 Ant 和 Gradle。

之前有接觸過 Grails 構建的 Java Web 項目,就是用 Gradle 來做依賴管理的。至於 Ant 也在剛工作的時候在一些老項目中有見到過,後面幾乎沒見過了。

Maven 文檔地址:https://maven.apache.org[1]

使用 Maven 可以讓我們快速構建一個新的項目,並且很方便的可以集成和管理多個三方的框架。當我們需要某個框架時可以去搜索一下這個框架的信息,然後配置到你的項目中即可。

搜索地址:https://mvnrepository.com[2]

比如我們想要使用 Spring Boot,除了在 Spring 的文檔中獲取依賴的版本,也可以自己去搜索,選擇對應的版本,如下圖:

可以看到默認就是 Maven 的依賴方式,只需要將 dependency 整段內容複製到項目的 pom.xml 文件中即可。右側還有很多其他的依賴方式,比如 Gradle 等。

 

 

Maven 依賴傳遞

今天主要講下如何去解決 Maven 做依賴管理的時候 Jar 包衝突的問題,在解決之前先來了解下基本的知識。

上圖展示了 Maven 的依賴傳遞性,首先是項目 B 中依賴了 Spring 和 Guava 兩個框架。然後項目 A 又依賴了項目 B,所以項目 A 也會依賴 Spring 和 Guava 兩個框架。

 

 

依賴傳遞 Jar 包選擇邏輯

依賴性傳遞會導致項目中依賴很多其他版本的 Jar,這種情況下怎麼進行 Jar 包的選擇呢?

有兩個規則:

  • 不同距離,距離近優先

  • 相同距離,前者優先

如下圖所示,項目依賴了項目 A 和項目 B,A 和 B 分別依賴了 Guava,但是從依賴層次來看,項目 B 的層次更淺,故 Guava18.0 會被優先選擇。

當距離相同的時候,就會優先選擇定義在前面的,如下圖所示,項目 A 和項目 B 都分別依賴了 Guava15.0 和 Guava18.0 的版本,但是項目 A 的順序在項目 B 的前面,所以會優先選擇 Guava15.0 版本。

通過依賴傳遞性經常會導致 Jar 包衝突的問題,比如下圖的項目 A 本身依賴了 Guava15.0,然後又依賴了項目 B,項目 B 中依賴了 Guava18.0,這樣項目 A 就會同時依賴 Guava15.0 和 Guava18.0。

如果剛好用到了高版本不兼容低版本的方法和類時,就會出現選擇錯誤,因爲 Maven 會根據依賴樹的深淺來選型淺的依賴,也就是 15.0。

 

 

衝突案例

下面就是一個典型的 Jar 包衝突問題,當一個 Jar 有多個版本的時候,就會出現衝突。

錯誤信息可以看到 com.google.common.collect.FluentIterable.concat 這個方法找不到,目前是從 guava-18.0.jar 中加載的,這種問題我們改怎麼解決呢?

Description:An attempt was made to call the method com.google.common.collect.FluentIterable.concat(Ljava/lang/Iterable;Ljava/lang/Iterable;)Lcom/google/common/collect/FluentIterable; but it does not exist. Its class, com.google.common.collect.FluentIterable, is available from the following locations:    jar:file:/Users/yinjihuan/.m2/repository/com/google/guava/guava/18.0/guava-18.0.jar!/com/google/common/collect/FluentIterable.classIt was loaded from the following location:    file:/Users/yinjihuan/.m2/repository/com/google/guava/guava/18.0/guava-18.0.jar
Action:Correct the classpath of your application so that it contains a single, compatible version of com.google.common.collect.FluentIterable

 

 

解決思路之懸絲診脈

找出衝突的 Jar,看看當前項目中依賴了哪幾個版本。

Eclipse

在 Eclipse 中可以雙擊 pom 文件,進入 Dependency 視圖,輸入你要搜索的 jar 名稱進行搜索,就可以看出當前項目中哪些框架依賴了你搜索的 jar,什麼版本都能知道。

Idea

Idea 中可以安裝 maven helper 插件來查看相關依賴信息,默認選中 Conflicts 會展示當前項目存在衝突的依賴,當然我們也可以直接查看樹形的依賴關係去分析衝突。

Maven 命令

不用不藉助於開發工具的插件,我們可以直接用 Maven 命令來查看當前項目的依賴關係,命令行進入到你要分析的項目目錄下,執行下面的命令將分析結果保存到文件中:

mvn dependency:tree > tree.log

執行完之後依賴的信息結構如下:

搜索了下 guava,發現在 smjdbctemplate 中依賴了 18.0 版本,這個框架是我自己基於 jdbctemplate 封裝的一個框架。

 

 

解決思路之察言觀色

其實很明顯,錯誤信息已經告訴我們 18.0 中找不到 concat 方法,所以 18.0 肯定是不能用的,通過前面的分析,找到了直接依賴 guava.18.0.jar 的是 smjdbctemplate,解決辦法就是將 smjdbctemplate 中的 guava 排除掉。​​​​​​​

<dependency>  <groupId>com.github.yinjihuan</groupId>  <artifactId>smjdbctemplate</artifactId>  <version>1.1</version>  <exclusions>    <exclusion>      <groupId>com.google.guava</groupId>      <artifactId>guava</artifactId>    </exclusion>  </exclusions></dependency>

還有就是根據依賴樹的深淺度來判斷當前項目依賴的是哪個版本,如下圖:

18.0 是最淺的,肯定是依賴它,其實在 Eclipse 裏面直接查看 Maven Dependencies 就可以指定當前項目依賴哪些框架和版本信息,如下圖:

當我們排除掉 18.0 後再來看依賴的版本是 20.0,如下圖:

根據依賴樹的深淺度,20.0 和 19.0 都是一樣的層級,但是 20.0 在 19.0 前面,所以優先選擇 20.0 版本。

再來看項目中的 pom 文件,發現 swagger 的聲明順序在 apollo 的前面。

 

如果我們把順序調整一下,那麼就會依賴 19.0 的版本。

 

 

總結

通過我仔細耐心的講解,妹子終於自己解決了遇到的問題,後面的事你們就猜去吧。😆

這種問題其實無法避免,當你依賴的三方框架越多的時候,衝突的可能性就越大。碰到問題的時候沉下心來仔細分析,藉助於工具幫助你排查問題。

當然我們在自己項目中去依賴三方的框架,也是要注意版本的問題,特別是對於多模塊的項目,每個子模塊都去依賴不同的版本,這樣很容易出問題,一般建議在父 pom 中 dependencyManagement 來統一管理版本,子模塊直接統一使用父 pom 中定義好的版本。

還有就是可以使用 optional (請看下文使用)來設置可選依賴,比如說你要封裝一個通用的模塊 Common,這個模塊中有很多通用的功能,項目 A 依賴只需要使用功能 A,項目 B 依賴只需要使用功能 B。每個功能都依賴了三方的 Jar,這個時候如果你不做任何處理,只要依賴了你這個通用的模塊 Common,那麼也就會間接依賴這兩個功能的第三方 Jar。這個時候可以通過設置 optional=true 來解決這個問題,我依賴了你的通用模塊 Common,如果我要使用 A 功能,那麼我必須顯示依賴 A 功能需要的三方依賴纔可以。

參考資料

[1]

maven:https://maven.apache.org/

[2]

mvnrepository:https://mvnrepository.com/

 

 

 

 

 

 

=========================================================================

=========================================================================

=========================================================================

 

 

 

Maven實戰-maven中的可選依賴(optional)_葵續淺笑的博客-CSDN博客
https://blog.csdn.net/lovejj1994/article/details/80283240

 

 

Maven實戰-maven中的可選依賴(optional)

 


在一些項目中,我們知道用exclusion排除一些依賴包,這屬於依賴排除(Dependency Exclusions),還有一種就是今天所說的可選依賴(Optional Dependencies)。主要還是講怎麼用。

準備工作
準備兩個工程,簡單點,就是A和B

只看POM文件,這是A的pom文件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>A</groupId>
    <artifactId>A</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.9.9</version>
        </dependency>
    </dependencies>
</project>

 


這是B的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>B</groupId>
    <artifactId>B</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>A</groupId>
            <artifactId>A</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

 


在這種情況下,joda-time包在B工程會被正常引用。

加入optional
在A工程對joda-time添加optional選項,這時在B工程中,joda-time依賴包會消失.

    <dependencies>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.9.9</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

 


如果想引這個包,需要在A項目中設optional爲false或者去除optional,或者在B項目中顯式調用。

parent 繼承的情況
如果A的pom像下面這樣配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>A</groupId>
    <artifactId>A</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>2.9.9</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

 


B再去引用的話,還是可以正常引用joda-time包,optional選項在統一控制版本的情況下會失效。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>B</groupId>
    <artifactId>B</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>A</groupId>
        <artifactId>A</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>    <dependencies>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
    </dependencies>
</project>

 

發佈了101 篇原創文章 · 獲贊 126 · 訪問量 107萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章