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

任何一個故事起因最重要

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

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


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

妹子遇到的是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

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

搜索地址:https://mvnrepository.com

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

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


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

Maven依賴傳遞

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

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


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

依賴傳遞Jar包選擇邏輯

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

有兩個規則:

  • 不同距離,距離近優先
  • 相同距離,前者優先

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

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


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

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


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

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

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


衝突案例

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

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

<code>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.class
It 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
/<code>

解決思路之懸絲診脈

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

Eclipse

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

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


Idea

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

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


Maven命令

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

<code>mvn dependency:tree > tree.log
/<code>

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

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


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

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


解決思路之察言觀色

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

<code>
    com.github.yinjihuan
    smjdbctemplate
    1.1
    
        
            com.google.guava
            guava
        
    

/<code>

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

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


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

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


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

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


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

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

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


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

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


總結

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

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

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

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

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


分享到:


相關文章: