Maven基礎教程之依賴

這又是一個系列,一個要把Maven講透的系列,但願可以對你們有幫助!

前言

在前面的總結中,老是說到依賴這個東西,並且還有看到dependencies這個詞在pom.xml文件中的使用,因此不少讀者就很火燒眉毛的想知道這個依賴究竟是什麼東西?做爲Maven中一個很是重要的概念,那到底該如何使用和配置,以及使用過程當中有哪些注意事項,而這篇文章就是對Maven中的依賴進行詳細的總結,一掃對依賴概念的不解。java

依賴的配置

在Maven中,是在pom.xml文件中完成依賴的配置,咱們先來看看依賴配置的語法。mysql

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

乍一看,這個配置仍是蠻複雜的,其實咱們經常使用的沒有這麼多,並且這些用起來也是很是簡單的。根元素project下的dependencies能夠包含一個或者多個dependency元素,以聲明一個或者多個項目依賴。下面就詳細說一下這些配置的含義。spring

  • groupIdartifactIdversion:依賴的基本座標,對於任何一個依賴來講,基本座標是最重要的,Maven根據座標才能找到須要的依賴;
  • type:依賴的類型,對應於項目座標定義的packaging,大部分狀況下,該元素沒必要聲明,其默認值爲jar;
  • scope:依賴的範圍,這個內容就比較多一點,下面會專門進行總結;
  • optional:標記依賴是否可選,下面會專門進行總結;
  • exclusions:用來排除傳遞性依賴,下面會專門進行總結。

不少時候,大部分依賴聲明只包含groupIdartifactIdversion這三個指定基本座標的元素;而在一些特殊狀況下,其它元素相當重要,也就是上面提到的scopeoptionalexclusions。下面就對這三個要素進行詳細的總結。sql

依賴範圍

不知道你們還記不記得在《Maven基礎教程之使用入門》中的這段junit依賴代碼:數據庫

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
</dependencies>

這裏就指定了scope這個要素的值,那這裏指定這個要素有什麼含義呢?apache

咱們須要知道,Maven在編譯項目主代碼的時候須要使用一套classpath。舉例來講:segmentfault

  • 當Maven編譯項目主代碼的時候若是須要用到spring-core,該文件以依賴的方式被引入到classpath中;
  • 當Maven編譯和執行測試的時候會使用另一套classpath,則junit文件也會以依賴的方式引入到測試使用的classpath中;
  • 當Maven項目運行時,又會使用一套classpath。

因此依賴範圍就是用來控制依賴與這三種classpath(編譯classpath、測試classpath、運行classpath)的關係。在Maven中,咱們能夠針對scope要素設置如下依賴範圍:api

  • compile:編譯依賴範圍。若是沒有指定scope值,就會默認使用該依賴。使用該依賴範圍的Maven依賴,對於編譯、測試、運行三種classpath都有效;
  • test:測試依賴範圍。使用此依賴範圍的Maven依賴,只對於測試classpath有效,在編譯主代碼或者運行項目時都沒法使用此依賴。對於上面的junit例子,它只有在編譯測試代碼及運行測試的用例的時候才須要;
  • provided:已提供依賴範圍。使用此依賴範圍的Maven依賴,對於編譯和測試classpath有效,但在運行時無效。最典型的例子是servlet-api,編譯和測試項目的時候須要該依賴,但在運行項目的時候,因爲容器已經提供,就不須要Maven重複地引入一遍;
  • runtime:運行時依賴範圍。使用此依賴範圍的Maven依賴,對於測試和運行classpath有效,但在編譯主代碼時無效。最典型的例子就是JDBC驅動實現,項目主代碼的編譯只須要JDK提供的JDBC接口,只有在執行測試或者運行項目的時候才須要實現上述接口的具體JDBC驅動。
  • system:系統依賴範圍。該依賴與三種classpath的關係和provided依賴範圍徹底一致。可是,使用system範圍的依賴時必須經過systemPath元素顯式地指定依賴文件的路徑。因爲此類依賴不是經過Maven倉庫解析的,並且每每與本機系統綁定,可能形成構建的不可移植,所以謹慎使用。system的使用舉例:微信

    <dependency>
        <groupId>com.jellythink.BookStore</groupId>
        <artifactId>BookStore-SSO</artifactId>
        <version>1.0</version>
        <scope>system</scope>
        <systemPath>${basedir}/lib/BookStore-SSO-1.0.jar</systemPath>
    </dependency>

    對於system系統依賴範圍,在進行以上配置之後,編寫代碼時已經能夠引入Jar包中的class了,可是在打包時,因爲scope=system,默認並不會將依賴包打進WAR包中,全部須要經過插件進行打包。例如:框架

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.10</version>
        <executions>
            <execution>
                <id>copy-dependencies</id>
                <phase>compile</phase>
                <goals>
                    <goal>copy-dependencies</goal>
                </goals>
                <configuration>
                    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/lib</outputDirectory>
                    <includeScope>system</includeScope>
                </configuration>
            </execution>
        </executions>
    </plugin>

會了更好的理解和記憶依賴範圍與classpath的關係,將上述內容總結成一張表格。

依賴範圍(scope) 對於編譯classpath有效 對於測試classpath有效 對於運行時classpath有效 例子
compile Y Y Y spring-core
test - Y - junit
provided Y Y - servlet-api
runtime - Y Y JDBC驅動實現
system Y Y - 本地的,Maven倉庫以外的類庫文件

依賴傳遞的幾個注意事項

說到依賴傳遞,這裏的關係就比較複雜,在沒有使用Maven以前,你們是否有這樣的開發體驗;好比引入了包A,因爲咱們不知道包A的依賴,只能在編譯的時候,根據出錯信息,再加入須要的其它依賴,很顯然,這樣的開發體驗是及其糟糕的。而如今有了Maven,Maven中的傳遞性依賴機制能夠很好的解決這一問題。這裏就來詳細的總結Maven中的依賴傳遞。

  • 啥是依賴傳遞
    在實際項目中,咱們確定會遇到以下圖所示的這種依賴狀況:

    maven依賴傳遞

    A依賴B,B又依賴C。因爲基於Maven建立的項目,有了傳遞性依賴機制,在使用A的時候就不用去考慮A依賴了什麼,也不用擔憂引入多餘的依賴。Maven會解析各個直接依賴的POM,將那些必要的間接依賴,以傳遞性依賴的形式引入到當前的項目中。

  • 依賴傳遞的範圍
    在傳遞性依賴中,如上圖所示的A依賴B,B依賴C,咱們就說A與B是第一直接依賴,B與C是第二直接依賴,C對於A是傳遞性依賴。第一直接依賴的範圍和第二直接依賴的範圍決定了傳遞性依賴的範圍,下面經過一張表格來講明這種傳遞性依賴範圍:

    maven傳遞性依賴範圍

    對於上面的圖,最左面的一列表示第一直接依賴範圍,最上面一行表示第二直接依賴範圍。好比A對B的依賴scope是compile,B對C的依賴scope是runtime,那麼A對C的依賴scope就是runtime。以下圖標註所示:

    maven傳遞性依賴範圍示例

    在實際使用過程當中,對於這個依賴傳遞範圍的關注仍是比較少的,在之後的使用過程當中,若是遇到問題,咱們應該能想到這裏總結的依賴傳遞範圍相關的知識點。

  • 依賴調解
    先來講一個實際開發過程當中常常會遇到的兩個問題。

    問題一:好比項目A有這樣的兩個依賴關係:

    maven依賴調解

    從上圖能夠看到,項目A有兩條依賴關係,X是A的傳遞性依賴,可是你會發現兩條依賴路徑上有兩個版本的X,那麼哪一個X會被Maven解析使用呢?兩個版本都被解析顯然是不對的,由於那會形成依賴重複,所以必須選擇一個。這種問題,該如何解決呢?

    問題二:好比項目A有這樣的兩個依賴關係:

    maven依賴調解

    對於這種狀況,和問題一的區別就是X做爲A的傳遞性依賴,兩個版本的依賴路徑長度是同樣的,都是2,這狀況下,那麼到底誰又會被解析呢?

    這裏就涉及到Maven中的依賴調解了,當出現上述的兩個問題時,Maven就會運用內置的兩個調解原則,肯定到底哪一個依賴會被最終解析使用。這兩個內置的調解原則以下:

    1. 路徑最短者優先;對於問題一,X(1.0)的路徑長度爲2,X(2.0)的路徑長度爲3,按照該路徑最短者優先原則,則X(1.0)會被解析使用;
    2. 第一聲明者優先;對於問題二,在依賴路徑長度相等的狀況下,在pom.xml中依賴聲明的順序決定了誰會被解析使用,順序最靠前的那個依賴優勝。
  • 可選依賴
    在Maven的世界裏,存在着這樣的一種依賴關係,以下圖所示:

    maven可選依賴

    從上圖能夠看到,M和N對於B都是可選依賴,依賴將不會進行依賴範圍傳遞,也就是說,若是A須要使用M或N時,還須要在pom.xml中顯示的進行聲明。

    既然可選依賴沒有依賴範圍傳遞,致使咱們須要人工不得不去進行一些顯示的依賴聲明,那爲何還要使用可選依賴這一特性呢?存在即合理!咱們想象這樣的一種場景,項目B實現了兩個特性,其中的一個特性依賴於M,另外一個特性依賴於N,並且這兩個特性是互斥的,用戶不可能同時使用這兩個特性。好比B是一個持久層隔離工具包,它支持多種數據庫,包括MySQL、PostgreSQL等,在構建這個工具包的時候,須要這兩種數據庫的驅動程序,但在使用這個工具包的時候,只會依賴一種數據庫。這種狀況下,項目B的依賴聲明就會是這樣的:

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.jellythink.BookStore</groupId>
        <artifactId>project-B</artifactId>
        <version>1.0.0</version>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.15</version>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <version>9.1-901-1.jdbc4</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </project>

    上述pom.xml代碼片斷中,使用<optional>元素表示mysql和postgresql這兩個依賴爲可選依賴,這樣一來,它們只會對當前的項目B產生影響,當其它項目依賴於B的時候,這兩個依賴不會被傳遞。所以,當項目A依賴於項目B的時候,若是其實際使用基於MySQL數據庫,那麼在項目A中就須要顯式地聲明mysql依賴,好比這樣:

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.jellythink.BookStore</groupId>
        <artifactId>project-A</artifactId>
        <version>1.0.0</version>
        <dependencies>
            <dependency>
                <groupId>com.jellythink.BookStore</groupId>
                <artifactId>project-B</artifactId>
                <version>1.0.0</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.15</version>
            </dependency>
        </dependencies>
    </project>

    看完上面關於可選依賴的總結,你們都會覺的可選依賴確實很是麻煩,原本使用Maven是爲了提高開發效率的,如今到好,搞的更復雜了;因此,在理想的狀況下,是不該該使用可選依賴的。經過上面的例子能夠看到,使用可選依賴的緣由是某一個項目實現了多個特性,在面向對象設計中,有個單一職責性原則,意指一個類應該只有一項職責,而不是糅合太多的功能。這個原則在規劃Maven項目的時候也一樣適用。這樣,對於上面的例子,更好的作法是爲MySQL和PostgreSQL分別建立一個Maven項目,基於一樣的groupId分配不一樣的artifactId,在各自的POM中聲明對應的JDBC驅動依賴,並且不使用可選依賴,這樣用戶則根據須要選擇使用對應的依賴便可。

實戰經驗分享

Maven的依賴涉及的知識點比較多,結合上面整理的內容,再經過結合前人的經驗,這裏分享一些實戰經驗,方便你們更好的理解和使用Maven。

  • 排除依賴
    經過上面的總結,你們能夠感覺到傳遞性依賴帶來的便利,可是有些時候這種特性也會帶來問題。你們想象一下如今的這個應用場景。當前項目有一個第三方依賴,而這個第三方依賴因爲某些緣由依賴了另一個類庫的SNAPSHOT版本,那麼這個SNAPSHOT就會成爲當前項目的傳遞性依賴,而SNAPSHOT的不穩定性會直接影響到當前項目。這時就須要排除該SNAPSHOT版本,而且在當前項目中聲明該類庫的某個正式發佈的版本。這種狀況就須要使用到Maven中的排除依賴了,經過exclusion關鍵字來排除對應的依賴。代碼片斷以下:

    <project>
        <modelVersion></modelVersion>
        <groupId>com.jellythink.BookStore</groupId>
        <artifactId>project-A</artifactId>
        <version>1.0.0</version>
        <dependencies>
            <dependency>
                <groupId>com.jellythink.BookStore</groupId>
                <artifactId>project-B</artifactId>
                <version>1.0.0</version>
                <exclusions>
                    <exclusion>
                        <groupId>com.jellythink.BookStore</groupId>
                        <artifactId>project-C</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>com.jellythink.BookStore</groupId>
                <artifactId>project-C</artifactId>
                <version>1.1.0</version>
            </dependency>
        </dependencies>
    </project>

    上面代碼的依賴邏輯以下圖所示:

    maven排除依賴

    項目A依賴於項目B,可是因爲某些緣由,不想引入傳遞性依賴C,而是本身顯式地聲明對於項目C 1.1.0版本的依賴。代碼中使用exclusions元素聲明排除依賴,exclusions能夠包含一個或者多個exclusion子元素,所以能夠排除一個或者多個傳遞性依賴。須要注意的是,聲明exclusion的時候只須要groupIdartifactId,而不須要version元素,這是由於只須要groupIdartifactId就能惟必定位依賴圖中的某個依賴。換句話說,Maven解析後的依賴中,不可能出現groupIdartifactId相同,而version不一樣的兩個依賴。

  • 歸類依賴
    如今不少應用都是基於Spring框架開發,可是使用Spring框架時,就須要引入多個Spring框架的依賴,好比這樣子的:

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.jellythink.BookStore</groupId>
        <artifactId>project-A</artifactId>
        <version>1.0.0</version>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
        </dependencies>
    </project>

    能夠看到,這些依賴都是Spring框架的不一樣模塊,在實際項目中,全部這些依賴的版本都是相同的,並且能夠預見,若是未來須要升級Spring框架,這些依賴的版本會一塊兒升級。按照上面的代碼片斷,咱們須要全部Spring框架模塊的version字段,這樣一個一個的修改增長了錯誤發生的機率。此時,咱們可使用Maven中的歸類依賴,經過引入Maven屬性來簡化這個問題。對於上面的代碼片斷,咱們能夠這樣修改:

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.jellythink.BookStore</groupId>
        <artifactId>project-A</artifactId>
        <version>1.0.0</version>
    
        <properties>
            <springframework.version>5.1.6.RELEASE</springframework.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>${springframework.version}</version>
            </dependency>
        </dependencies>
    </project>

    這裏使用properties元素定義Maven屬性,在pom.xml中可使用美圓符號和大括弧環繞的方式引用Maven屬性。

  • 優化依賴
    在軟件開發的過程當中,咱們經過工具來簡化咱們的工做,提高咱們的工做效率,而Maven就是這樣的一個工具。對於工具,有的時候,咱們不能僅僅停留在使用的層面,而是可以掌控工具。

    經過前面的總結,咱們知道Maven會自動解析全部項目的直接依賴和傳遞性依賴,而且根據規則正確判斷每一個依賴的範圍,對於一些依賴衝突,也能進行調節,以確保任何一個構件只有惟一的版本在依賴中存在。在這些工做以後,最後獲得的那些依賴被稱爲「已解析依賴」。咱們能夠經過如下命令查看當前項目的已解析依賴以及依賴範圍:

    mvn dependency:list

    咱們還能夠經過如下命令查看項目的依賴樹,經過這棵依賴樹就能很清楚地看到某個依賴是經過哪條傳遞路徑引入的:

    mvn dependency:tree

    使用dependency:listdependency:tree能夠幫助咱們詳細瞭解項目中全部依賴的具體信息,在此基礎上,還有dependency:analyze工具能夠幫助分析當前項目的依賴。

    mvn dependency:analyze

    dependency:analyze命令的輸出結果中有兩個重要的部分。首先是Used undeclared dependencies,指的是項目中使用到的,可是沒有顯式聲明的依賴。這種依賴是經過直接依賴傳遞進來的,當升級直接依賴的時候,相關傳遞性依賴的版本也可能發生變化,這種變化不易察覺,可是有可能致使當前項目出錯;所以,顯式聲明任何項目中直接用到的依賴。

    另外一個重要的部分是Unused declared dependencies,指項目中未使用的,可是顯式聲明的依賴。須要注意的是,對於這樣一類依賴,咱們不能簡單的直接刪除其聲明,而是應該仔細分析。因爲dependency:analyze命令只會分析編譯主代碼和測試代碼須要用到的依賴,一些執行測試盒運行時須要的依賴它就沒法發現。固然了,有時候確實能經過該信息找到一些沒用的依賴,可是必定要當心測試。

總結

如今回頭一看,Maven中依賴的知識點還真很多,雖然Maven中關於依賴的知識點不少,可是實際用到的,須要咱們重點關注的內容是不多的,本文中有些概念咱們只須要知道和理解就OK了。這篇文章很是的長,內容多,知識點也多,但願你們仍是有點耐心,好好的閱讀一下,爭取一次就把Maven的依賴概念理解到位,一次學會,終生受益。但願你能喜歡這篇文章。

果凍想,玩代碼,玩技術!

2019年4月7日,於內蒙古呼和浩特。


微信掃碼關注公衆號

相關文章
相關標籤/搜索