1. 程式人生 > >maven系列-(二)maven依賴

maven系列-(二)maven依賴

我們在用maven的時候,最常接觸到的功能就是專案依賴了,我們在pom檔案裡,指定依賴的各種jar包,maven就可以自動的找到jar包,下載到本地。我們的專案就可以正常運行了。
在引入包的時候,一般都是這樣引入的:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

這樣就可以引入spring-core和spring-context和junit三個依賴了。
maven既然能夠根據pom檔案中的這些標籤,自動的引入依賴,那就需要定義一套規則。可以唯一的標識要引入的包,這就有了座標的概念。

座標:

maven座標的元素包括groupId、artifactId、version、packaging、classifier,只要是能夠提供正確的座標,maven就可以找到對應的依賴。maven內建了一箇中央倉庫的地址(http://repo1.maven.org/maven2)),該倉庫中就包含了全世界大部分流行的開源專案構建,maven在需要的時候就會去這個倉庫下載。當然,在開發專案的時候,依然要為自己的專案指定座標,這樣其他的專案就可以通過座標來引用了。

那定義座標的這幾個元素都代表什麼意思呢?
groupId:定義了專案屬於哪個組,一般和專案所在的組織或公司存在關聯
artifactId:定義了專案在組中唯一的id,一般就是專案名
version:定義了版本號。如果version說明是xxx-SNAPSHOT說明這是一個快照,該專案還處於開發中,是不穩定的版本。
packaging:定義了maven專案打包的方式,一般為jar或war。預設為jar。
classifier:定義構建輸出的一些附屬構件,不能直接定義,是由附加的外掛幫助生成的。
有了這幾個元素,就可以唯一確定相應的構件依賴了。

依賴:

上面引入的依賴,還有一個更完整的寫法:

<dependencies>
    <dependency>
        <groupId>...</groupId>
        <artifactId>...</artifactId>
        <version>...</version>
        <type>...</type>
        <scope>...</scope>
        <optional>...</optional>
        <exclusions>
            <exclusion>
                ...
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

這樣就是一個完整的引入依賴的方式,其中,groupId、artifactId、version自然就不必說了,type是標識依賴的型別,對應專案座標定義的packaging,一般不需要宣告,預設為jar。scope是依賴的範圍,optional標識依賴是否可選,exclusions用來排除傳遞性依賴。
一般引入依賴是,可選的依賴範圍scope有compile、test、provided、runtime、system和import。預設是compile。因為我們專案引入的那些依賴是整個生命週期需要的依賴,但是編譯的時候,可能只需要一部分依賴,執行單元測試的時候,也需要一部分依賴。比如,在引入Junit依賴的時候,一般宣告scope為test就可以了。

compile是編譯依賴範圍,也是預設的依賴範圍,對編譯、測試和執行都有效。
test是測試依賴範圍,只對測試有效。
provided是已提供依賴範圍,對編譯和測試有效,執行時無效。
runtime是執行時依賴範圍,測試和執行時有效,編譯無效。
system是系統依賴範圍,和provided範圍一致,但需要用systemPath顯示指定依賴檔案路徑,一般不用。
import是匯入依賴範圍。

我們一開始的例子中,引入了spring-core和spring-context、junit三個依賴,如果在idea中點選進入依賴的話,會發現,其實裡面還定義了很多的依賴。spring-core是spring框架的基石,它為spring框架提供了基礎的支援。所以裡面還會有很多依賴。
我們在maven中,只需要引入spring-core的依賴,那spring-core本身需要的那些依賴,maven也會自動的引入,不需要我們再手動的引入一遍了。這就是maven依賴中的傳遞性依賴。
舉個例子,A依賴於B,B依賴於C,A對B就是第一直接依賴,B對C就是第二直接依賴,A對C就是傳遞性依賴。
但是,傳遞性依賴和依賴的範圍也是有關係的,第一直接依賴和第二直接依賴的範圍,決定了傳遞依賴的範圍。如果我在A中依賴B,範圍是test,B依賴C,範圍還是test,那麼,A是不會傳遞性依賴C的。
傳遞性依賴的範圍,可以通過下面的表格來說明,左邊一列表示第一直接依賴,上面一行表示第二直接依賴,中間的結果表示傳遞性依賴。

compile test provided runtime
compile compile - - runtime
test test - - test
provided provided - provided provided
runtime runtime - - runtime

有了傳遞性依賴,我們在pom檔案中引入依賴就變得簡單多了,一般只需要引入需要直接依賴就可以了,剩下的maven自動會引入。但是,這種方式有時也會造成問題,在除錯的時候,就會發現引入的依賴版本不符合預期,這就需要去進行依賴調解了。
在idea右側的maven欄,點選show dependencies,可以看到所有的依賴。
在這裡插入圖片描述
我們在專案中引入了spring-core、spring-context和junit,就可以看到對應的依賴圖。
在這裡插入圖片描述

因為我們專案一共就引入了三個依賴,所以依賴圖比較簡單,如果引入的依賴多的話,依賴圖就會很複雜。
在依賴圖中,就可以看到自己依賴的包版本號,找到問題的所在,把不需要傳遞依賴的包通過exclusions去掉。

在解決問題之後,還需要考慮下,為什麼會出現引入的依賴不符合預期呢,這就因為傳遞依賴時,可能多個包都會依賴同一個包,而且版本還不一樣,但maven不可能把這個包所有的版本都引入,最終只會引入一個包,所以需要遵循一定的原則。
maven傳遞依賴的原則要遵循最短路徑和最先宣告。先判斷依賴的路徑長度,最短的優先,如果長短一樣,再判斷宣告順序,先宣告的優先。

舉個例子,有兩個依賴路徑A->B->C, A->D->E->C,這兩個依賴路徑引入的C的版本號不一樣。
對於A->B->C這種依賴關係,A依賴B,B依賴C,所以這條依賴路徑上,A對C的傳遞依賴路徑長度是2。
對於A->D->E->C這種依賴關係,A對C的依賴路徑長度為3。比上面A->B->C的路徑長度要大,所以,最終C的版本,按照A->B->C引入的依賴版本。
那對於路徑長度一樣的情況呢,比如兩個依賴路徑A->B->C, A->D->C,這兩個路徑,對C的依賴長度都是2,所以,就需要看宣告的順序了,第一宣告優先,如果A依賴B的宣告在A依賴D的宣告之前,則C的版本,就會按照A->B->C路徑中宣告的版本。

參考資料:
1.《maven實戰》 許曉斌 著