Maven學習筆記(四):協調和依賴

Maven協調具體的解釋:

     Maven定義了這樣一組規則:世界上不論什麼一個構件都可以使用Maven座標惟一標識。Maven座標的元素包含groupId、artifactId、version、packaging、classifier。咱們僅僅需要提供正確的座標元素,Maven就能找到相應的構件。比方當需要使用Java5平臺上的TestNG的5.8版本號時,就告訴Maven:」groupId=org.testng; artifactId=testng; version=5.8; classifer=jdk15,maven就會從中央倉庫( http://search.maven.org/#browse)中尋找對應的構件供咱們使用。
     先看一組座標定義。例如如下:
     <groupId>org.sonatype.nexus</groupId>
     <artifactId>nexus-indexer</artifactId>
     <version>2.0.0</version>
     <packaging>jar</packaging>
     nexus-indexer是一個對Maven倉庫編纂索引並提供搜索功能的類庫,它是Nexus項目中的一個子模塊。後面會具體介紹Nexus。

如下詳解一下各個座標元素:html

     groupId:定義當前Maven項目隸屬的實際項目。首先,Maven項目和實際項目不必定是一對一的關係。比方SpringFramework這一實際項目。其相應的Maven項目會有很是多,如spring-core、spring-context等。一個實際項目通常會劃分紅多個項目模塊。

groupId不該該僅僅相應於項目隸屬的組織或公司,緣由是一個組織下會有很是多實際項目,假設groupId僅僅定義到組織級別,後面可以看到。artifactId僅僅能相應Maven項目。那麼實際項目這個層將難以定義。java

最後,groupId的表示方式與java包名的表示方式相似,一般與域名反向一一相應。mysql

      artifactId:該元素定義了實際項目中的一個Maven項目(模塊)。推薦的作法是使用實際項目名稱做爲artifactId的前綴。

比方上例的artifactId是nexus-indexer,使用了實際項目名nexus做爲前綴。這樣作的優勢是方便尋找實際構件。spring

      version:該元素定義了Maven項目當前所處的版本號。

實際上,Maven定義了一套完整的版本號規範,以及快照(SNAPSHOT)的概念。在後面的章節將具體討論。sql

     packaging:該元素定義Maven項目的打包方式。首先,打包方式一般與所生成構件的文件擴展名相應,如上例中packaging爲jar,終於的文件名稱爲nexus-indexer-2.0.0.jar。而是用war打包方式的Maven項目。終於生成的構件會有一個.war文件。但這不是絕對的。

當不定義packaging時,Maven會是用默認值jar。數據庫

      classifier:該元素用來幫助定義構件輸出的一些附屬構件。

附屬構件與主構件相應。如上例中的主構件是nexus-indexer-2.0.0.jar。該項目還會經過一些插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar這樣一些附屬構件,其包括了Java文檔和源碼。這時候,javadoc和sources就是這兩個附屬構件的classifier。這樣。附屬構件也就擁有了本身惟一的座標。注意:不能直接定義項目的classifier,因爲附屬構件不是項目直接默認生成的,而是由附加的插件幫助生成。api

     項目構件的文件名稱是與座標相相應的,通常的規則是artifactId-version[-classifier].packaging,[-classifier]表示可選。這裏還要強調一點,packaging並非必定與構件擴展名相應,比方packaging爲maven-plugin的構件擴展名爲jar。

依賴的配置:

     一個依賴聲明可以包括例如如下的一些元素:
<project>
     ...
     <dependencies>
          <dependency>
               <groupId>...</groupId>
               <artifactId>...</artifactId>
               <version>...</version>
               <type>...</type>
               <scope>...</scope>
               <optional>...</optional>
               <exclusions>
                    <exclusion>
                         ...
                    </exclusion>
                ...
               </exlusions>
          </dependency>
          ...
     </dependencies>
     ...
</project>
     根元素project下的dependencies可以包括一個或者多個denpendency元素,以聲明一個或者多個項目依賴。

每個依賴可以包括的元素有:maven

  • groupId、artifactId和version:依賴的基本座標,對於不論什麼一個依賴來講,基本座標是最重要的,Maven依據座標才幹找到需要的依賴。
  • type:依賴的類型,相應於項目座標定義的packaging。大部分狀況下。該元素沒必要聲明,其默認值爲jar。
  • scope:依賴的範圍。請見後面小節
  • optional:標記依賴是否可選,請見後面小節
  • exclusions:用來排除傳遞性依賴,請見後面小節

Maven依賴範圍:

     在Maven中,依賴範圍用元素 scope表示。Maven在運行編譯、測試、運行時運行的是三套不一樣的classpath。

依賴範圍就是用來控制依賴與這三種classpath(編譯classpath、測試classpath、執行classpath)的關係。Maven有下面幾種依賴範圍:
      compile:編譯依賴範圍。

假設沒有指定,就會默認使用該依賴範圍。該此依賴範圍對於編譯、測試、執行三種classpath都有效。典型的樣例是spring-core,在編譯、測試和執行的時候都需要使用該依賴。ide

      test:測試依賴範圍。

該依賴範圍僅僅對於測試classpath有效。在編譯主代碼或者執行項目的時將沒法使用此類依賴。典型的樣例就是JUnit。它僅僅有在編譯測試代碼及執行測試環境的時候才需要。工具

      provided:已提供依賴範圍。該依賴範圍對於測試和執行class-path有效,但在執行時無效。典型的樣例是servlet-api。編譯和測試項目的時候需要該依賴。但在執行項目的時候。由於容器已經提供,就不需要Maven反覆引入一遍。
     runtime:執行時依賴範圍。

該範圍依賴,對於執行和測試class-path有效,但在編譯主代碼時無效。典型的樣例是JDBC驅動實現。項目主代碼的編譯僅僅需要JDK提供的JDBC接口,僅僅有在執行測試或者執行項目的時候才需要實現上述接口的詳細的JDBC驅動。

     system:系統依賴範圍。該依賴與三種classpath的關係,和provided依賴範圍全然一致。但是,使用system範圍的依賴時必須經過systemPath元素顯式地指定依賴文件的路徑。由於此類依賴不是經過Maven倉庫解析的,而且每每與本機系統綁定,可能形成構建的不可移植,所以應該慎重使用。

systemPath元素可以引用環境變量。如:

<dependency>
     <groupId>javax.sql</groupId>
     <artifactId>jdbc-stdext</artifactId>
     <version>2.0</version>
     <scope>system</scope>
     <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
      import: 導入依賴範圍。該依賴範圍不會對三種classPath產生實際的影響,咱們將在後面的章節具體介紹該依賴。

     

傳遞性依賴:       

何爲傳遞性依賴:
     傳遞性性依賴的意思是項目A依賴了B構件。而在B構件的pom.xml中又顯式的依賴了C構件。那麼A項目也就會依賴到C構件。在不使用Maven的項目其中,咱們一般需要手動的去尋找所有直接使用和間接使用的構件(傳遞性依賴)。以及解決版本號衝突的問題,這將耗費很是大的精力且意義不大。
     Maven的傳遞性依賴機制可以很是好的解決這一問題。在A項目下有一個org.springframework:spring-core:2.5.6的依賴,而實際上spring-core也有它本身的依賴,好比spring-core-2.5.6.pom該文件包括了一個commos-logging依賴,見如下代碼:
     <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifact>
          <version>1.1.1</version>
     </denpendency>
     commons-logging沒有聲明依賴範圍,那麼其依賴範圍就是默認的compile。而spring-core通常的依賴範圍也是compile。
     A項目有一個compile範圍的spring-core依賴。spring-core有一個compile範圍的commons-logging依賴。那麼commons-logging就會成爲A項目的compile範圍依賴。commons-logging是account-email的一個傳遞性依賴。
     有了傳遞性依賴機制,在使用Spring Framework的時候就不用去考慮它依賴了什麼,也不用操心引入多於的依賴。

Maven會解析各個直接依賴的POM。將那些必要的間接依賴,以傳遞性依賴的形式引入到當前的項目之中。


傳遞性依賴和依賴範圍:
     若是A依賴與B,B依賴與C,咱們說A對於B是第一直接依賴,B對於C是第二直接依賴。A對於C是傳遞性依賴。第一直接依賴的範圍和第二直接依賴的範圍決定了傳遞性依賴的範圍。

例如如下表所看到的,最左邊一列表示第一直接依賴範圍。最上面一行表示第二直接依賴範圍。中間的交叉單元格則表示傳遞性依賴範圍。

  compile test provided runtime
compile compile     -         - runtime
test test     -         - test
provided provided     -         - provided
runtime runtime     -         - runtime
     細緻觀察該表,可以發現例如如下的規律:當第二直接依賴的範圍是compile的時候。傳遞性依賴的範圍與第一直接依賴的範圍一致。當第二直接依賴的範圍是test的時候,依賴不會得以傳遞;當第二直接依賴的範圍是provided的時候,僅僅傳遞第一直接依賴範圍也爲provided的依賴,且傳遞性依賴的範圍相同爲provided;當第二直接依賴的範圍是runtime的時候。傳遞性依賴的範圍與第一直接依賴的範圍一致,但compile例外。此時傳遞性依賴的範圍爲runtime。

依賴調解:

     Maven的傳遞性依賴機制,一方面大大簡化和方便了依賴聲明。

但有時候形成問題時。咱們需要知道該傳遞性依賴是從哪條依賴路徑引入的。

     好比,項目A有這種依賴關係:A->B->C->X(1.0)、A->D->X(2.0),X是A的傳遞性依賴,但是兩條依賴路徑上有兩個版本號的X,那麼哪一個X會被Maven解析使用呢?兩個版本號都被解析是不行的,因爲會形成反覆依賴。Maven依賴的第一原則是:路徑近期者優先。該例中X(1.0)的路徑長度爲3,而X(2.0)的路徑長度爲2。所以X(2.0)會被解析使用。
     Maven定義了依賴調解的第二原則:第一聲明者優先。在依賴路徑長度相等的前提下,在POM中依賴聲明的順序決定了誰會被解析使用。順序最靠前的那個依賴優勝。

可選依賴:

     若是有下面狀態的依賴:A->B、B->X(可選)、B->Y(可選)。依據傳遞性依賴的定義,若是所有這三個依賴的範圍都是compile,那麼X、Y就是A的compile範圍傳遞性依賴。

然而。由於這裏X、Y是可選依賴,依賴將不會得以傳遞。

     使用可選依賴的緣由多是B實現了兩個特性,當中的特性一依賴於X。特性二依賴於Y,而且這兩個特性是相互排斥的,用戶不可能同一時候使用這兩個特性。比方B是一個持久層隔離工具包,它支持多種數據庫,包含MySQL,PostgreSQL等,在構建工具包的時候。需要這兩種數據庫的驅動程序。但在使用這個工具包的時候。僅僅會依賴一種數據庫。
     項目B的依賴聲明見例如如下:
<project>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.juvenxu.mvnbook</groupId>
     <artifactId>project-b</artifactId>
     <version>1.0.0</version>
     <dependencies>
          <dependency>
               <groupId>mysql</groupId>
               <artifactId>mysql-connector-java</artifactId>
               <version>5.1.10</version>
               <optional>true</optional>
          </dependency>
          <dependency>
               <groupId>postgresql</groupId>
               <artifactId>postgresql</artifactId>
               <version>8.4-701.jdbc3</version>
               <optional>true</optional>
          </dependency>
     </dependencies>
</project>     
     在pom.xml中。使用 <optional>元素表示mysql-connector-java和postgresql這兩個依賴爲可選依賴,它們僅僅對當前項目B產生影響,當其它項目依賴於B的時候,這兩個依賴不會被傳遞。所以,當項目A依賴於項目B的時候,假設事實上際使用基於MySql數據庫。那麼在A項目中就想要顯示地聲明mysql-connector-java這一依賴,見如下A項目pom.xml。
<project>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.juvenxu.mvnbook</groupId>
     <artifactId>project-a</artifactId>
     <version>1.0.0</version>
     <dependencies>
          <dependency>
              <groupId>com.juvenxu.mvnbook</groupId>
              <artifactId>project-b</artifactId>
              <version>1.0.0</version>
          </dependency>
          <dependency>
               <groupId>postgresql</groupId>
               <artifactId>postgresql</artifactId>
               <version>8.4-701.jdbc3</version>
          </dependency>
     </dependencies>
</project>     
       在理想狀況中,是不該該使用可選依賴的。使用可選依賴的背景是一個項目實現了多個特性,在面向對象的設計中,有個單一職責性原則,意指一個類應該僅僅有一項職責,而不是糅合太多的功能。

在上面的樣例中,更好的作法是爲MySql和PostgreSQL分別建立一個Maven項目。基於相同的groupId分配不一樣的artifactId,如com.juvenxu.mvnbook;project-b-mysql和com.juvenxu.mvnbook:project-b-postgresql,在各自的POM中聲明相應的JDBC驅動依賴,而且不適用可選依賴,用戶則依據需要選擇使用project-b-mysql或者project-b-postgresql。由於傳遞性依賴的做用。就再也不聲明JDBC驅動依賴。

     

最佳實踐:

排除依賴:
     傳遞性依賴儘管簡化了項目依賴的管理,但有時也會帶來一些問題,需要咱們排除一些傳遞性依賴。好比:當前項目有一個第三方依賴,而這個第三方依賴依賴了還有一個類庫的SNAPSHOT版本號。那麼這個SNAPSHOT就會成爲當前項目的傳遞性依賴。而SNAPSHOT的不穩定性會直接影響到當前的項目。這時候就需要排除掉該SNAPSHOT,並且在當前的項目中聲明該類庫的某個正式公佈的版本號。

     另外一些狀況,你也可能需要排除依賴,比方SUN JTA API,Hibernate依賴於這個JAR。但是由於版本號的因素,該類庫不在中央倉庫中。而Apache Geronimo項目中有一個對應的實現。這時你就可以排除Sun JAT API。再聲明Geronimo的JTA API實現。
     排除依賴範例代碼例如如下:
<project>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.juvenxu.mvnbook</groupId>
     <artifactId>project-a</artifactId>
     <version>1.0.0</version>
     <dependencies>
          <dependency>
              <groupId>com.juvenxu.mvnbook</groupId>
              <artifactId>project-b</artifactId>
              <version>1.0.0</version>
              <exclusions>
                   <exclusion>
                        <groupId>com.juvenxu.mvnbook</groupId>
                        <artifactId>project-c</artifactId>
                   </exclusion>
              </exclusions>
          </dependency>
          <dependency>
              <groupId>com.juvenxu.mvnbook</groupId>
              <artifactId>project-c</artifactId>
              <version>1.1.0</version>
          </dependency>
     </dependencies>
</project>     
     代碼中使用exclusions元素聲明排除依賴,exclusions可以包括一個或者多個exclusion子元素,所以可以排除一個或者多個傳遞性依賴。


歸類依賴:
     很是多時候。咱們會使用到來自同一項目下的不一樣模塊,而且這些依賴的版本號都是一樣的。好比咱們在使用spring framework時,分別引入的依賴爲org.springframework:spring-core:2.5.六、org.springframework:spring-beans:2.5.六、org.springframework:spring-context:2.5.六、org.springframework:spring-support:2.5.6。假設未來需要升級Spring Frame-work,這些依賴的版本號會一塊兒升級。     
     在Maven中可以使用歸類依賴,這樣可以避免反覆,而且在改動值的時候,可以減小發生錯誤的概率。樣例例如如下:
<project>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.juvenxu.mvnbook</groupId>
     <artifactId>account-email</artifactId>
     <name>Account Email</name>
     <version>1.0.0-SNAPSHOT</version>
     
     <properties>
          <springframework.version>2.5.6</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-support</artifactId>
              <version>${springframework.version}</version>
          </dependency>
     </dependencies>
</project>     
     這裏用到了Maven屬性(後面會具體介紹Maven屬性)。Maven執行的時候會將POM中的所有的${springframework.version}替換成實際值2.5.6。

也就是說。可以使用美圓符號和大括弧圍繞的方式引用Maven屬性。而後,將所有Sping Framework依賴的版本號值用這一屬性引用表示。


優化依賴:

     Maven會本身主動解析所有項目的直接依賴和傳遞性依賴,並且依據規則正確推斷每個依賴的範圍。對於一些依賴衝突,也能進行調節。以確保不論什麼一個構件僅僅有惟一的版本號在依賴中存在。在這些工做以後。最後獲得的那些依賴被稱爲已解析依賴。可以執行例如如下的命令查看當前項目的已解析依賴:
      mvn dependency:list
     在此基礎上。還能進一步瞭解已解析依賴的信息。將直接在當前項目POM聲明的依賴定義爲頂層依賴,而這些頂層依賴的依賴則定義爲第二層依賴,...當這些依賴經Maven解析後,就會構成一個依賴樹。經過這棵依賴樹就能很是清楚地看到某個依賴是經過哪條路徑引入的。可以執行 mvn dependency:tree來查看當前項目的依賴樹。

     咱們還可以使用 mvn dependency:analyze來分析項目中的依賴。

使用該工具可以得出兩類內容:

Used undeclared dependencies
     意指項目中使用到的。但是沒有顯式聲明的依賴。這樣的依賴意爲着潛在的風險。當前項目直接在使用它們。好比有很是多相關Java import聲明,而這樣的依賴是經過直接依賴傳遞進來的。當升級直接依賴的時候,相關傳遞性依賴的版本號可能發生改變,接口就可能發生改變。那麼就會致使當前項目中的相關代碼沒法編譯。

所以。通常應該顯式聲明不論什麼項目中直接用到的依賴。

Unused declared dependencies
     意指項目中未使用的,但是顯式聲明的依賴。對於這一類依賴,咱們應該認真的分析,由於mvn dependency:analyze僅僅會分析編譯主代碼和測試代碼需要用到的依賴。一些執行測試盒執行時需要的依賴它發現不了。因此咱們應該認真分析該依賴是否會被用到再決定對該依賴的取捨。
相關文章
相關標籤/搜索