Maven依賴分析

背景

昨天幫一位同事排查了一個依賴衝突的問題。問題的現象就是在IntelliJ IDEA運行項目正常,可是打包(Maven assembly jar)以後傳到服務器運行失敗,報錯:Caused by: java.lang.NoSuchFieldError: INSTANCEhtml

後來定位到某個類存在多個版本,其中一個版本是沒有INSTANCE的。進一步發現項目所依賴的其餘module,都是以assembly jar的形式install到本地倉庫的,最終經過修改pom文件,對所依賴的module從新install,使其安裝到本地倉庫的是原始的、不包含依賴的jar。至此,問題解決。java

在排查的過程當中,發現了一些有趣的現象,後來又本身研究了下,現把結果記錄下來,以供分享。apache

兩種分析依賴的方式

這裏先介紹兩種依賴分析的方式。服務器

Maven支持打印當前項目的依賴樹,命令是mvn dependency:tree。下面是一個demo:maven

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ m-c ---
[INFO] com.heyikan.demo:m-c:jar:1.0-SNAPSHOT
[INFO] \- com.heyikan.demo:m-b:jar:1.0-SNAPSHOT:compile
[INFO]    \- com.heyikan.demo:m-a:jar:1.0-SNAPSHOT:compile

從這裏能夠看到,m-c依賴於m-b,m-b依賴於m-a。工具

須要注意這個命令是基於倉庫進行依賴解析的,也就是說若是要解析m-c項目的依賴,那麼必須確保它所依賴的全部項目均可以在倉庫中找到。即便m-c和m-b是同一個項目的不一樣module,若是m-b沒有安裝到本地倉庫,這個命令也會失敗。ui

另外,這個命令打印出來的是最終的依賴結果:對於同一個項目的多個版本只會打印被選擇的那一個。插件

另外一種方式是IntelliJ IDEA的功能,支持以圖形的方式展現項目的依賴圖譜。打開項目的pom文件,使用快捷鍵Ctrl+Shift+Alt+U打開當前的圖譜:code

在圖譜頁面使用Ctrl+F快捷鍵,能夠搜索指定的依賴。xml

這個命令不要求全部的依賴都已安裝在本地倉庫,對於同一個項目的不一樣module之間的依賴,能夠直接解析

並且同一個依賴的不一樣版本依賴都會在圖譜中展現出來,其中被選擇的那個是黑線相連,其餘的是紅線。選中其中一個,會出現被棄用的依賴版本到最終選擇的版本的一條連線。

注意這二者的區別,它表示使用Maven命令處理Maven項目的方式,和使用IDEA工具直接處理Maven項目的方式是有差異的。這種差異通常都會很微妙,而且是形成開發環境運行正常,可是服務器上運行失敗的可能緣由。

Maven依賴調解機制

下面的內容參考自許曉斌的《Maven實戰》。

由於Maven的傳遞依賴,極可能致使依賴的衝突,這種衝突的具體形式表如今同一個項目的不一樣版本都出如今項目的依賴圖譜中。

針對這種狀況,Maven有依賴調解的規則。

首先是路徑最近者優先,舉例來講,若是項目A存在這樣的依賴關係:A -> B -> C -> X(1.0) 和 A -> D -> X(2.0)。項目X有兩個版本的依賴出現,此時由於X(1.0)的依賴長度爲3,X(2.0)的依賴長度爲2,最終被採用的依賴時2.0版本的X。

當第一個規則沒法區別同一個依賴項目的不一樣版本時,使用第二個規則:位置靠前的優先。好比A -> B -> Y(1.0)和A -> C -> Y(2.0),最終會選擇Y(1.0)。

有趣的是,若是直接在項目中聲明一個項目的兩個版本的依賴,如A -> Z(1.0)和A -> Z(2.0),則最後的會覆蓋前面的。

shade插件對依賴的影響

shade插件用於製造一個包含依賴的assembly jar包。

默認的狀況下,shade插件的打包包名會佔用Maven原生的打包包名,若是將插件打包目標綁定到生命週期的package階段,那麼install階段安裝到本地倉庫的其實是shade插件打出來的assembly jar。

並且,這個assembly jar的pom文件已經改變,pom文件中不包含任何的依賴,由於全部的依賴都已經在它裏面了。

好比說,我建立一個項目demo-dependency,它有三個模塊m-am-bm-c,依賴關係爲m-c -> m-b -> m-a。其中m-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">
    <parent>
        <artifactId>demo-dependency</artifactId>
        <groupId>com.heyikan.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>m-b</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.heyikan.demo</groupId>
            <artifactId>m-a</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

注意,它使用了shade插件,並將打包目標shade綁定到了package階段。

執行mvn clean install以後,去本地倉庫看下這個項目的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/maven-v4_0_0.xsd">
  <parent>
    <artifactId>demo-dependency</artifactId>
    <groupId>com.heyikan.demo</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>m-b</artifactId>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.4.3</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

注意,已經沒有依賴的內容了。

此時,使用mvn dependency:tree分析m-c的命令,結果以下:

--- maven-dependency-plugin:2.8:tree (default-cli) @ m-c ---
[INFO] com.heyikan.demo:m-c:jar:1.0-SNAPSHOT
[INFO] \- com.heyikan.demo:m-b:jar:1.0-SNAPSHOT:compile

試想一下,m-c項目依賴於m-b,而m-b是一個assembly jar,那麼全部m-b依賴的項目最終都會被視作m-b自己。若是你想把m-c也打成一個assembly jar,如何處理m-b的依賴和其餘依賴鏈上的衝突?恐怕沒法獲得什麼保證。

shade插件的這種行爲,實際上干擾了Maven的依賴調解機制。

要規避這個問題,最簡單的方式是爲shade插件的打包結果自定義名稱,避免和Maven標準包名衝突:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <finalName>${project.build.finalName}-assembly</finalName>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

注意,configuration -> finalName配置了自定義的jar包名稱。

擴展閱讀

  1. Java項目打包方式分析
  2. Maven核心知識
相關文章
相關標籤/搜索