一小時教你學會 Maven 項目的構建與管理(1)

  • 2020 年 3 月 18 日
  • 筆記

Maven翻譯成中文是「專家、內行」。Maven是Apache組織中一個頗為成功的開源項目,Maven主要服務於基於Java平台的項目構建、依賴管理和項目信息管理的優秀工具。

—————–來自小馬哥的故事


Maven是優秀的構建工具:自動化構建過程、跨平台、標準化構建過程。

Maven為Java開發者提供了一個免費的中央倉庫,其中幾乎可以找到任何流行的開源類庫,通過Maven的衍生工具Nexus,可以進行快速的搜索。Maven項目目錄結構有約定的規則,約定優於配置(Convention Over Configuration)。

Ant(Another Neat Tool)另一個整潔的工具,Tomcat構建,過程式,開發者需要顯式的指定每一個目標以及完成該目標所需要執行的任務,每一個項目都需要重新編寫這一過程。

Maven是聲明式的,項目構建過程和過程各階段所需工作都要插件實現,大部分插件都是現成的,開發者只需要聲明項目的基本元素,Maven就可以執行內置的,完整的構建過程。

Maven 基礎環境配置與基本命令

Maven的安裝與配置分析

Maven環境的安裝

安裝環境:Jdk1.7、Maven-3.5.0

第一步:JDK安裝與配置。Windows和Linux下安裝步驟可自行查找。

第二步:Maven的下載。官方下載地址:http://maven.apache.org/download.cgi

當前最新版本3.5.0,Windows上安裝下載apache-maven-3.5.0-bin.zip

Linux上安裝下載 apache-maven-3.5.0-bin.tar.gz

Windows上安裝Maven步驟:

1.解壓apache-maven-3.5.0-bin.zip到D:developapache-maven-3.5.0

2.配置環境變量

    M2_HOME=D:developapache-maven-3.5.0        Path末尾添加;% M2_HOME%bin

3.測試安裝是否正確

在命令行執行mvn –v,可以maven版本信息和基本配置信息表示配置成功。

Linux上安裝Maven步驟:
  • 解壓 tar xzvf apache-maven-3.5.0-bin.tar.gz
  • 配置環境變量 export M2_HOME = /home/develop/apache-maven-3.5.0 export PATH = $PATH;$M2_HOME/bin
  • 測試安裝是否正確 echo $M2_HOME 輸出環境變量路徑 mvn -v 輸出版本號等相關信息

安裝目錄分析

說明

bin:該目錄下mvn、mvnDebug是基於Linux平台的shell腳本,mvn.bat、mvnDebug.bat是基於Windows平台的bat腳本,在命令行輸入mvn命令時實際上是調用mvn或mvn.bat腳本。mvn和mvnDebug的區別是mvnDebug多了一條MAVENDEBUGOPTS配置,作用是運行Maven時開啟debug模式以調試Maven本身。m2.conf文件是classworlds的配置文件,Maven啟動時會自動加載。

boot: 該目錄只包含一個文件plexus-classworlds-2.5.2.jar,plexus-classworlds是一個類加載器框架,相對於默認的java類加載器,它提供了更加豐富的語法以便配置,Maven使用該框架加載自己的類庫。

conf: 該目錄包含了Maven的配置文件settings.xml,可以指定2種級別:全局級別:直接修改${maven.conf}/settings.xml文件可以全局定製Maven的行為,對一台機器上的所有用戶有效。用戶級別:將該文件複製到${user.home}/.m2/目錄下,然後修改settings.xml配置,在當前用戶範圍內定製Maven的行為。

lib: 該目錄包含了所有Maven運行時需要的Java類庫,Maven本身是分模塊的maven-*.jar都是maven自己的包,還有很多第三方依賴包。

LICENSE: Maven使用的軟件許可證是Apache LicenseVersion 2.0。

NOTICE: Apache Maven Distribution使用的第三方軟件。

README.txt: Maven的簡明介紹,包括系統要求、安裝說明、Maven URLS等。

Maven的基本命令

Maven項目構建過程中,主要構建命令有幾種:

  • mvn validate 驗證,驗證項目是正確的並且所有的信息是可用的;
  • mvn clean 清理,清理項目緩存輸出,一般是target文件夾被刪除;
  • mvn compile 編譯,將java源文件編譯成.class文件;
  • mvn test 測試,生成測試報告,運行test目錄下的所有單元測試;
  • mvn package 打包,將項目打成jar、war或者pom;
  • mvn install 安裝,將當前項目安裝到本地maven庫,供其他項目依賴;
  • mvn deploy部署,在構建環境中完成,複製最終的包到遠程庫。

執行後面的命令會自動執行前面的命令,比如執行mvn package時會執行validate、clean、compile、test、package五個階段。

Maven 核心概念理論

Maven概念模型與依賴解析機制

Maven根據項目的pom.xml文件,把它轉化成項目對象模型(POM),這時要解析依賴關係,然後去相對應的maven庫中查找所依賴的jar包。在clean,compile,test,package等生命周期階段都有相應的Plug-in來做這些事情,而這些Plug-in會產生一些中間產物。

Maven根據項目的pom.xml文件,把它轉化成項目對象模型(POM),這時要解析依賴關係,然後去相對應的maven庫中查找所依賴的jar包。在clean,compile,test,package等生命周期階段都有相應的Plug-in來做這些事情,而這些Plug-in會產生一些中間產物。

Maven從倉庫解析依賴的機制

當本地倉庫沒有依賴構件的時候,Maven會自動從遠程倉庫下載;當依賴版本為快照版本時,Maven會自動找到最新的快照。

1.當依賴範圍scope=system時,Maven直接從本地文件系統解析構件;

2.根據依賴坐標計算倉庫路徑後,嘗試直接從本地倉庫尋找構件,若發現構件則解析成功;

3.在本地倉庫不存在相應構件的情況下,若依賴版本是顯式的發佈版本構件時,如1.1.0、1.2-alpha-1等,則便利所有的遠程倉庫,發現後下載到本地倉庫並解析使用;

4.如果依賴的版本是RELEASE或者LASTEST,則基於更新策略讀取所有遠程倉庫的元數據groupId/artifactId/maven-metadata.xml,將其與本地倉庫的對應元數據合併後,計算出RELEASE或者LASTEST的真實值,然後基於真實值檢查本地和遠程倉庫;

5.如果依賴版本是SNAPSHOT,則基於更新策略讀取所有遠程倉庫的元數據groupId/artifactId/version/maven-metadata.xml,將其與本地倉庫的對應元數據合併後,得到最新快照版本的值,然後基於該值檢查本地或者從遠程倉庫下載;

6.如果最後解析到的構件版本是時間戳格式的快照,如1.0-20170712.191220-2,則複製其時間戳格式的文件至非時間戳格式,如SNAPSHOT,並使用該非時間戳格式的構件。

當依賴的版本不明晰的時候,如RELEASE、LASTEST、SNAPSHOT,Maven就需要基於更新遠程倉庫的更新策略來檢查更新。

Maven倉庫

構件:在Maven的世界,任何一個依賴、插件或者項目構建的輸出,即xxx.jar;任何一個構件都有一組坐標唯一標識。

倉庫:得益於坐標機制,任何Maven項目使用任何一個構件的方式都是完全相同的,在此基礎上,Maven可以在某個位置統一存儲所有Maven項目共享的構件,這個統一的位置就是倉庫。

public class DefaultRepositoryLayout      implements ArtifactRepositoryLayout  {      private static final char PATH_SEPARATOR = '/';      private static final char GROUP_SEPARATOR = '.';      private static final char ARTIFACT_SEPARATOR = '-';      public String getId()      {          return "default";      }        public String pathOf( Artifact artifact )      {          ArtifactHandler artifactHandler = artifact.getArtifactHandler();            StringBuilder path = new StringBuilder( 128 );            path.append( formatAsDirectory( artifact.getGroupId() ) ).append( PATH_SEPARATOR );          path.append( artifact.getArtifactId() ).append( PATH_SEPARATOR );          path.append( artifact.getBaseVersion() ).append( PATH_SEPARATOR );          path.append( artifact.getArtifactId() ).append( ARTIFACT_SEPARATOR ).append( artifact.getVersion() );            if ( artifact.hasClassifier() )          {              path.append( ARTIFACT_SEPARATOR ).append( artifact.getClassifier() );          }            if ( artifactHandler.getExtension() != null && artifactHandler.getExtension().length() > 0 )          {              path.append( GROUP_SEPARATOR ).append( artifactHandler.getExtension() );          }            return path.toString();      }        private String formatAsDirectory( String directory )      {          return directory.replace( GROUP_SEPARATOR, PATH_SEPARATOR );      }  }

例如:groupId=com.feiyue、artifactId=demo、version=1.0、artifactId=jdk7、packaging=jar 其對應的路徑生成如下:

1.groupId路徑:formatAsDirectory()將groupId中的'.'轉換成'/',com.feiyue就會轉換成com/feiyue,之後再加一個'/',就變成com/feiyue/

2.artifactId路徑:在groupId基礎的加上artifactId,再加上一個'/,變成com/feiyue/demo/

3.version路徑:在前面基礎上加上version,再加上一個'/,變成com/feiyue/demo/1.0/

4.依次加上artifactId、一個'-』、version,就變成com/feiyue/demo/1.0/demo-1.0

5.如果有classfier,4)會變成com/feiyue/demo/1.0/demo-1.0-jdk7

6.如果extension存在則依次加上'.』、extension。代碼中extension是從artifactHandler而非artifact中獲取,artifactHandler是由packaging決定的。故packaging決定了構件的擴展名,因此最終的路徑為com/feiyue/demo/1.0/demo-1.0-jdk7.jar

Maven倉庫的分類

Maven倉庫分為兩類:本地倉庫和遠程倉庫。當Maven根據坐標尋找構件時,首先會查看本地倉庫,若本地倉庫存在此構件則直接使用;若本地倉庫不存在此構件,Maven就會去遠程倉庫查找,查找到下載到本地倉庫再使用。若本地倉庫和遠程倉庫都沒有需要的構件,Maven就會報錯。

中央倉庫: Maven核心自帶的遠程倉庫,包含了絕大部分開源構件,默認情況,當本地倉庫沒有Maven需要構件時,就從中央倉庫下載。

私服:一種特殊的遠程倉庫,為節省帶寬和時間,應在局域網內架設一個私有倉庫服務器,用其代理所有外部的遠程倉庫。

本地倉庫:用戶自定義本地倉庫的地址,需編輯${user.home}/.m2/setting.xml文件,設置localRepository節點的值為倉庫地址即可,默認情況下${user.home}/.m2/setting.xml是不存在的,需要用戶從安裝目錄複製${M2_HOME}/conf/setting.xml文件在進行編輯。

<settings>  <localRepository>E:/maven/repository</localRepository>  </settings>

中央倉庫: Maven默認的遠程倉庫,安裝文件中自帶了中央倉庫的配置,在${M2_HOME}/lib/maven-model-builder-3.2.5.jar中,解壓縮找到orgapachemavenmodelpom-4.0.0.xml,可以看到如下默認遠程倉庫配置:

<repositories>    <repository>     <id>central</id>     <name>Central Repository</name>     <url>https://repo.maven.apache.org/maven2</url>     <layout>default</layout>     <snapshots>         <enabled>false</enabled>     </snapshots>    </repository>  </repositories>

這個配置文件是所有Maven項目都會繼承的超級POM.

私服:特殊的遠程倉庫,架設在局域網內的倉庫服務,代理公網的遠程倉庫,當Maven需要下載構件時,從私服請求,若私服不存在該構件,則從公網遠程倉庫下載,緩存到私服之後,再為Maven的下載請求提供服務。另外無法從公網倉庫下載的構件也能從本地上傳到私服供項目使用。

私服優點:節省外網帶寬、提供Maven構件速度、部署第三方構件、提供Maven構件穩定性、降低中央倉庫負荷。

Maven坐標

唯一標識Maven構件,坐標元素分為groupId、artifactId、version、packaging、classifier.

groupId:必選,定義當前Maven項目隸屬的實際項目,不一定是一對一的關係,通常一個實際項目會被劃分成很多模塊。groupId一般不應該只定義到公司級別,一個公司可能會有很多實際項目,如果groupId只定義到組織級別,那麼artifactId只能定義Maven項目。命名方式和Java包名類似,域名反向一一對應。例如:org.springframework.

artifactId:必選,定義實際項目中的一個Maven模塊,推薦使用實際項目名稱-模塊名稱,這樣便於找到某個項目的一組構件。例如:spring-core,spring-beans,spring-web等。

version:必選,定義Maven項目當前所處的版本。例如:4.3.9.RELEASE、1.0-SNAPSHOT、RELEASE、LATEST、2.1等。

packaging:可選默認是jar,定義Maven項目的打包方式。打包方式有jar、war、pom等。

classifier:不能直接定義,幫助定義構建輸出的一些附屬構件。附屬構件與主構件對應,例如-javadoc.jar、-sources.jar附屬構件包含了java文檔和源代碼。

依賴管理

依賴管理分為傳遞性依賴、依賴調解、可選依賴、排除依賴、歸類依賴等。

傳遞性依賴

maven模塊 -> spring-jdbc -> spring-core -> commons-logging

假設: A -> B -> C,即A對B是第一直接依賴,B對C是第二直接依賴,A對C是傳遞性依賴,第一直接依賴(簡稱F)和第二直接依賴(簡稱S)的範圍決定了傳遞性依賴(簡稱T)的範圍。如圖所示:

例如:A -> B -> C,A依賴B的範圍是test,B依賴C的範圍是compile,則A傳遞依賴C的範圍是test。

結論:當S=compile時,T與F的範圍一致;當S=test時,依賴不會傳遞;當S=provided時,只有當F=provided時,T=provided;當S=runtime時,T=F,但F=compile例外,此時T=runtime.

依賴調解

依賴調解第一原則:路徑最近者優先。

例如:A -> B -> C -> X1 長度為3 A -> D -> X2 長度為2,因此X2會被解析使用 依賴調解第二原則:第一原則優先,依賴路徑相等時,POM中依賴聲明順序靠前的優先。

例如:A -> B -> X1 長度為2 A -> C -> X2 長度為2,但是POM文件中B的依賴聲明靠前,因此X1會被解析使用。

可選依賴

A依賴於B,B依賴於X和Y,B對於X和Y的依賴都是可選依賴,即optional=true

AB、BX(可選)、BY(可選)。可選依賴不會傳遞,即X、Y對A沒有影響。

可選依賴一般是多種互斥的特性,具體使用時只選其一。

排除依賴

使用exclusions元素聲明排除依賴,exclusions包含一個或者多個exclusion子元素,因此可以排除一個或者多個傳遞性依賴。注意聲明exclusion時只需要groupId和artifactId,而不需要version元素,因為只需要groupId和artifactId就可以唯一定位依賴圖中的某個依賴。

歸類依賴

spring的依賴包版本都是相同的,可以使用properties元素定義Maven屬性spring.version=4.x

在定義依賴時可以使用美元符號加大括弧環繞的方式來引用Maven屬性,例如${spring.version}。

聚合與繼承

聚合:多個項目或者模塊聚合到一起,建立一個package方式為pom的項目parent專門負責聚合工作,並使用modules-module指定子模塊,目的是快速構建項目。

繼承:多個模塊聚合時,子模塊需要繼承父模塊以消除重複配置。

聚合與繼承的共同點是聚合POM與繼承關係中的父POM的packaging都必須是pom。

聚合關係與繼承關係的比較如下圖所示:

本文由 小馬哥 創作,採用 知識共享署名4.0 國際許可協議進行許可 本站文章除註明轉載/出處外,均為本站原創或翻譯,轉載前請務必署名 最後編輯時間為: 2017/11/23 09:22