Maven實戰、5.座標與依賴

5.1 何爲Maven座標

        世界上任何一個構件都可以使用Maven座標唯一標識,Maven座標的元素包括groupId、artifactId、version、packageing、classfier。

Maven內置了Maven倉庫:http://repo1.maven.org/maven2

5.2  座標詳解

        <groupId>org.sonatype.nexus</group>
       <artifactId>nexus-indexer</artifactId>
       <version>2.0.0</version>
      <packaging>jar<packaging>
        groupId:定義當前Maven項目類屬於的實際項目,Maven項目與實際項目不一定是一對一的關係。比如SpringFramework這一實際
項目,其對應的Maven項目會有很多,如Spiring-core、Spring-context等,這是由於Maven中模塊的概念,因此,一個實際的項目往往
會被劃分成多個模塊。其次,groupId不應該對應項目隸屬的組織或公司。原因很簡單,一個組織下會有多個實際項目,如果groupId只
定義到組織級別,而後面的artifactId只能定義Maven項目(模塊),那麼實際項目這個層將難以定義。
       artifactId:該元素定義實際項目中的一個Maven項目(模塊),推薦做法是使用實際項目名稱作爲artifactId的前綴,比如例子中的
nexus-indexer,默認情況下,Maven生成的構件,其文件名會默認以artifactId作爲開頭,使用實際項目名稱作爲前綴之後,就能方便的
從一個項目的文件夾下面找到一組構件。
      version:該元素定義Maven項目當前所處的版本,如上例中版本是2.0.0,Maven定義了一套完整的版本規範,以及快照(SNAPSHOT)
的概念,後續會詳細進行介紹。
      packaging:該元素定義了Maven的打包方式。首先,打包方式通常與所生成構件的文件擴展名對應,如例子中的packaging爲jar,
最終的文件名是nexus-indexer-2.0.2.jar,而使用war打包方式的Maven項目,最終生成的構件會有一個.war文件,不過這不是絕對的。
其次,打包方式會影響到構件的生命週期,比如jar打包和war打包會使用不同的命令。最後不定義packaging的時候,Maven會使用
默認的值jar.
     classifier:該元素i用來定義構件輸出的一些附屬構件。附屬構件與主構件對應。如上列中的主構件爲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,因爲附屬構件不是由項目默認生成的,而是由附加的插件幫助生成。

5.3 account-email

         案例中有一個email模塊負責發送賬戶激活的電子郵件。Maven實戰的代碼放到了我的資源下面。
        pom文件如下:
<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">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lxm.mkfriend</groupId>
  <artifactId>mkfriend-email</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>email</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>2.5.6</version>
    </dependency>
    
    <dependency>    
       <groupId>org.springframework</groupId>
       <artifactId>spring-core</artifactId>
       <version>2.5.6</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>2.5.6</version>
    </dependency>
    
    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context-support</artifactId>
       <version>2.5.6</version>
    </dependency>
    
    <dependency>
	    <groupId>javax.mail</groupId>
	    <artifactId>mail</artifactId>
	    <version>1.4.1</version>
    </dependency>
    
    <dependency>
        <groupId>com.icegreen</groupId>
        <artifactId>greenmail</artifactId>
        <version>1.3.1b</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.7</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  
  <build>
      <plugins>
         <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-compiler-plugin</artifactId>
             <configuration>
                <source>1.5</source>
                <target>1.5</target>
             </configuration>
         </plugin>
      </plugins>
  </build>
</project>

5.4 依賴的配置

        dependency除了包含,groupId,artifactId,version等基本元素外,還會包含一些其他的元素。
    <dependency>
       <groupId></groupId>
       <artifactId></artifactId>
       <version></version>
       <type></type>
       <scope></scope>
       <optional></optional>
       <exclusions>
          <exclusion></exclusion>
       </exclusions>
    </dependency>
groupId、artifactId、version:依賴的基本座標,對於任何一個依賴來說,基本座標是最重要的,Maven可以根據座標才能找到需要的依賴。
type:依賴的類型,對應項目座標定義的packaging。大部分情況下,該元素不必聲明,其默認值爲jar。
scope:依賴的範圍
optional:標記依賴是否可選。
exclusions:用來排除傳遞依賴。

5.5 依賴的範圍

    範圍用scope表示。首先需要知道Maven在編譯主代碼時需要使用一套classpath。在account-email實例中,編譯主代碼需要用到spring-core,
該文件以依賴的方式被引入到classpath中。其次,Maven在編譯與執行測試的時候使用另一套classpath。上例中的JUnit就是一個很好的例子。
該文件也以依賴的方式引入到測試使用的classpath中,不同的是這裏的依賴範圍是test。最後實際運行Maven項目的時候,又會使用一套classpath,
上例中的spring-core需要在classpath中,而JUnit不需要。
    依賴範圍就是用來控制依賴於這三種classpath(編譯classpath、測試classpath、運行classpath)的關係:Maven有如下幾種依賴類型:
complile:編譯依賴範圍。如果沒有指定,就會默認使用該依賴範圍。使用此依賴範圍的Maven依賴,對於編譯、測試、運行三種classpath都有效。
                   典型的例子就是spring-core,在編譯,測試和運行都需要。
test:測試依賴範圍。使用此依賴範圍的Maven依賴,只對於測試classpath有效,在編譯主代碼或者運行項目的使用時無法使用此類依賴。典型的例子
         是Junit,它只有在編譯測試代碼及運行測試代碼時需要。
provided:已提供依賴範圍。使用此範圍的Maven依賴,對於編譯和測試classpath有效,但在運行時無效。典型的例子是servlet-api,編譯和測試項目
          的時候需要該依賴,但在運行項目的時候,由於容器已經提供,就不需要Maven重複地引入一遍。
runtime:運行時依賴範圍。使用此依賴範圍的Maven依賴,對於測試和運行的classpath有效,但在編譯主代碼時無效。典型的例子是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:(Maven2.0.9及以上):導入依賴範圍。該依賴不會對三種classpath產生實際的影響,將在介紹8.3.3 Maven依賴時進行詳細介紹。

5.6 傳遞性依賴

5.6.1 何爲傳遞依賴

        考慮一個基於Spring Framework的項目,如果不使用Maven,那麼在項目中就需要手動下載相關依賴。由於Spring Framework又會依賴於其他開源類庫,因此
實際中往往會下載個很大的如spring-framework-2.5.6-with-dependencies.zip的包,這裏包含了所有的spring-Framework的jar包,以及所有它依賴的其他jar包。
這麼做往往就引入了很多不必要的依賴。另一種做法就是指下載spring-framework-2.5.6.zip這樣一個包,這裏不包含其他相關依賴,到實際使用的時候,在根據
出錯信息,或者查詢相關文檔,加入需要的其他依賴,顯然,這也是一件非常麻煩的事情。
       Maven的傳遞性依賴機制很好地解決了這個問題。已account-email項目爲例,該項目有一個org.springframwork:spring-core.2.5.6的依賴,而實際上spring-core
也有自己的依賴,我們可以直接訪問位於中央倉庫的該構建的POM:http://repo1.maven.org/maven2/org/springframework/spring-core/2.5.6/spring-core-2.5.6.pom,
該文件包含了一個commons-logging依賴,
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
        該依賴沒有聲明依賴範圍,那麼其依賴範圍就是默認的compile。同時回顧一下account-email,spring-core的依賴範圍也是compile。
        account-mail有一個compile範圍的spring-core依賴,spring-core有一個compile範圍的依賴commons-logging依賴,那麼commons-logging
就會成爲account-email的complile範圍依賴,commons-logging是account-email的一個傳遞性依賴:
      account-emai---->spring-core----->commons-logging
                             ----------------------------->
    有了這個傳遞性依賴機制,在使用Spring Framework的時候就不用去考慮它依賴了什麼,也不用擔心引入多餘的依賴。Maven會解析各個直接依賴
POM,將那些必要的間接依賴,以傳遞性依賴的形式引入到當前的項目中。

5.6.2 傳遞性依賴和依賴範圍 

        依賴範圍不僅可以控制依賴與三種classpath 的關係,還對傳遞性依賴產生影響。

第一直接依賴/第二直接依賴      complile      test         provided       runtime
complile(測試、編譯、運行)     complile       X              X                    runtime
test(測試)                                      test                X              X                    test
provided(測試,編譯)                provided       X              X                    provided
runtime(測試、運行)                  runtime         X               X                     runtime

5.7 依賴調節

        例如項目A存在依賴關係,A->B->C->X(1.0)、A->D->X(2.0) 第一原則路徑最短的優先,因此X(2.0)會被解析使用。
       A->B->Y(1.0),A-C->Y(2.0),第二原則,第一聲明者優先,如果B的聲明依賴在C前,則Y(1.0)會被解析使用。

5.8 可選依賴

         項目A依賴於項目B,項目B依賴於項目X和Y,B對於X和Y的依賴都是可選依賴:A->B、B->X(可選)、B->Y(可選)。根據傳遞性依賴的定義,如果
所有第三個依賴範圍都是compile,那麼X,Y就是A的compile範圍傳遞性依賴。然而,由於這裏X,Y是可選依賴,依賴將不會得以依賴。換句話說,X,Y
將不會對A有任何影響。
       爲什麼要使用這一特性呢?可能項目B實現了兩個特性,其中的特性一依賴於X,特性二依賴於Y,而且這兩個特性是互斥的,用戶不可能同時使用兩個特
性。比如B是一個持久層隔離工具包,它支持多種數據庫,包括MySQL、PostgreSQL等,在構建這個工具包的時候,需要這兩種數據庫的驅動程序,但在使
用這個工具包的時候,只會依賴一種數據庫。
 <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <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>

   使用<optional>標識依賴是可選依賴,他們只對當前項目B產生影響,當其他項目依賴於B的時候,這兩個依賴不會被傳遞。因此,當項目A依賴於項目B的時候,如果其實際使用基於Mysql數據庫,那麼需要在項目A中就需要顯式地聲明mysql-connector-java這一個依賴。

5.9 最佳實踐

       本小節歸納Maven依賴常見的一些技巧,方便用來避免和處理很多常見的的問題。

5.9.1  排除依賴

        傳遞性依賴會給項目隱式引入很多依賴,這極大的簡化了項目依賴的管理,但是有些時候這些特性也會帶來問題。例如當前項目有一個第三方
依賴,而第這個第三方依賴由於某種原因依賴了另外一個類庫的SNAPSHOT版本,那麼這個SNAPSHOT就會成爲當前項目的傳遞性依賴,而
SNAPSHOT版本的穩定性會直接影響到當前項目,這時就需要排除該SNAPSHOT,並且在該項目中聲明該類庫的某個正式發佈的版本。
       還有一種情況,你可能也想要替換某個傳遞性依賴,比如Sun JTA API,Hibernate 依賴於這個JAR,但是由於版權的因素,該類庫不在中央
倉庫中,而Apache Geronimo項目中有一個對應的實現,這是就可以排除Sun JTA API,在聲明Geronimo的JTA API的實現。
        
<project>
<groupId>com.liuxm.com</groupId>
<artifactId>project-a</artifactId>
<version></version>
<dependencies>
    <dependency>
          <groupId>com.liuxm.group</groupId>
          <artifactId>project-b</artifactId>
          <version>1.0.0</version>
          <exclusions>
              <exclusion>
                   <groupId>com.liuxm.group</groupId>
                   <artifactId>project-c<artifactId>
              </exclusion>
          </exclusions>
   </dependency>
    <dependency>
           <groupId>com.liuxm.group</groupId>
           <artifactId>project-c</artifactId>
           <version>1.1.0</version>
    </dependency>
</dependencies>
</project>
       
      上述代碼中,項目A依賴項目B,但是由於一些原因,不想引入傳遞性依賴C,而是自己顯示聲明對項目C1.1.0的依賴,代碼中
使用exclusions元素聲明排除依賴,exclusions可包含一個或多個exclusion,因此可以排除一個或者多個傳遞性依賴。需要注意的是,
聲明exclusion的時候只需要groupId和artifactId,而不需要version元素,這是因爲只需要groupId和artifactId就能唯一定位依賴圖中某個
依賴。換句話說,Maven解析後的依賴中,不可能出現groupId和artifactId相同,但是version不同的兩個依賴。
A----->B-----X----C(version?)
   -----C(version1.10)

5.9.2 歸類依賴

         在前面已經提過,有很多關於spring framework的依賴,它們分別是org.springframework:spring-cre:2.5.6/org
.springwork:spring-beans:2.5.6、org.springframework:spring-context:2.5.6和org.springframework:spring-context-support:2.5.6,
它們來自同一項目的不同模塊。因此,所有這些依賴的版本都是相同的,而且可以預見,如果將來需要升級spring framework,
這些依賴版本會一起升級。
      
<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">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lxm.mkfriend</groupId>
  <artifactId>guileidependency</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>guileidependency</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <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-context-support</artifactId>
        <version>${springframework.version}</version>
    </dependency>
  </dependencies>
</project>
       這裏簡單用了Maven屬性,首先使用properties元素定義Maven定義屬性,該例中定義了一個springframework.version子元素,其值爲2.5.6.
有了這個屬性定義之後,Maven運行的時候會將POM中的所有的${springframwork.version}替換成實際值2.5.6.

5.9.3 優化依賴

       通過本章內容,已經瞭解到,Maven會自動解析所有項目的直接依賴和傳遞性依賴,並且根據規則正確判斷每個依賴的範圍,
對於一些依賴衝突,也能進行調節,也確保任何一個構件只有唯一的版本在依賴中存在。在這些工作之後,最後得到的那些依賴
被稱爲已解析依賴。可以運行當前項目的已解析依賴:
mvn dependency:list
mvn dependency:tree
mvn dependency:analyze


發佈了124 篇原創文章 · 獲贊 32 · 訪問量 67萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章