使用maven-pom進行依賴管理與自動構建
使用maven-pom進行依賴管理與自動構建
- 使用maven-pom進行依賴管理與自動構建
- 一、讓我們先了解一下maven
- 1.maven是優秀的依賴管理工具
- 2.maven是優秀的構建工具
- 3.maven是約定大於配置、簡單易學的工具
- 二、pom基本結構
- 1.概覽
- 2.專案座標
- 3.package標籤
- 4.專案間關係
- 5.dependencies標籤
- 6.dependencyManagement標籤
- 7.Properties標籤
- 三、配置構建任務
- 1.maven構建的生命週期
- 2.外掛的“構建階段”
- 3.build標籤
- 4.profiles標籤
- 5.project標籤下的build標籤
- 5.Reporting標籤
- 一、讓我們先了解一下maven
使用maven-pom進行依賴管理與自動構建
最後編輯於 :pencil::{docsify-updated}
一、讓我們先了解一下maven
1.maven是優秀的依賴管理工具
隨著開發生態環境的不斷髮展,幾乎所有的java應用都會使用第三方的類庫,尤其是在這個開源的世界裡, java應用依賴管理已經很難再由人工完成——依賴管理包括解決依賴傳遞、版本衝突、依賴臃腫等問題。
maven通過groupId
、artifactId
、version
形成的座標定位系統能準確的定位每一個構件(artifact), 開發者可以通過在pom檔案中列出所依賴的構件的座標,讓maven工具從maven倉庫中自動下載所有需要的構件; 另一方面,通過pom檔案間的依賴傳遞、繼承等方式降低依賴管理的難度。
2.maven是優秀的構建工具
在我們的開發過程中,除了編寫程式碼以外,有很大一部分時間是花在編譯、執行單元測試、生成文件、打包和部署這些工具上, 為也提高工作效率,使開發人員能更多的將精力用於開發工作,我們需要像maven這樣的工具, 如流水線般的將所有部署以自動化的方式完成。
3.maven是約定大於配置、簡單易學的工具
在java開發中,常用的構建工具有Ant、maven、gradle三種。
3.1 Ant
Ant是過程式的,開發者需要顯示的指定每個目標,以及完成該目標鎖需要執行的任務。
Ant不僅限於對java專案進行構建,也可以對其他語言(如C語言)的專案進行構建。
<project name="MyProject" default="dist" basedir="../../../../../../../ProgramFiles/JetBrains/IntelliJ IDEA 2019.3.2/jbr/bin"> <description> simple example build file </description> <!-- set global properties for this build --> <property name="src" location="src"/> <property name="build" location="build"/> <property name="dist" location="dist"/> <target name="init"> <!-- Create the time stamp --> <tstamp/> <!-- Create the build directory structure used by compile --> <mkdir dir="${build}"/> </target> <target name="compile" depends="init" description="compile the source"> <!-- Compile the Java code from ${src} into ${build} --> <javac srcdir="${src}" destdir="${build}"/> </target> <target name="dist" depends="compile" description="generate the distribution"> <!-- Create the distribution directory --> <mkdir dir="${dist}/lib"/> <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file --> <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/> </target> <target name="clean" description="clean up"> <!-- Delete the ${build} and ${dist} directory trees --> <delete dir="${build}"/> <delete dir="${dist}"/> </target> </project>
與maven、gradle不同,ant本身沒有對應的中央倉庫,如果想使用maven一樣, 通過宣告的方式管理依賴,並自動處理依賴管理等問題,則使用整合ivy外掛管理依賴。 但ant不支援多模組的管理方式,而maven和gradle支援。
ant的這種宣告方式,使開發者可以根據自己的需要靈活配置,定製自己的專案構建流程; 另一方面,這種靈活的配置方式,也使得ant的配置相對繁瑣,可讀性較差,學習成本也更高 ——事實上,ant的官方網站上列出的外掛就有139種,而且部分工具的官網已經404了。
3.2 maven
maven與gradle一樣,都是宣告式的配置方式,相對於Ant來說,配置更加方便。 同時maven有內建生命週期,約定了專案的程式碼結構,只須簡單配置,就可以完成構建任務。
頂級pom中定義的專案結構(即預設專案結構)
專案根目錄
|-- src
| |-- main
| | |-- java -> 主程式碼檔案
| | |-- resources -> 主資原始檔
| | -- scripts -> 雖然在maven的預設結構裡有這個目錄,但官網文件中已刪除了這一級目錄,不建議使用
| -- test
| |-- java -> 測試程式碼檔案
| -- resources -> 測試資原始檔
-- pom.xml
小提示:可通過mvn -DarchetypeCatalog=internal archetype:generate
初始化標準結構的maven專案
另一方面,maven內建生命週期,使用無法更加專案的構建順序,如果想在構建中加上一些其他處理邏輯, 則需要用編寫maven外掛的方式來完成,成本相對高昂。
3.3 Gradle
gradle是相對新穎的構建工具,它使用一種基於Groovy的特定領域語言(DSL)來宣告專案設定, 目前也增加了基於Kotlin語言的kotlin-based DSL,拋棄了基於XML的各種繁瑣配置。
gradle沒有maven般的生命週期,真正起作用的是所引入的Plugin,因此相對更為靈活。
另一方面,gradle作為新生工具,無論是程式還是文件都在不斷的完善中,使用和學習相對困難。 同時,由於gradle的配置是基於groovy/kotlin語法的,因此使用gradle需要掌握相應的語法知識。
gradle是對ant和maven特點折衷的結果,相對於ant其可讀性更好,相對於maven其更為靈活。
二、pom基本結構
POM是project object mode的簡寫,maven通過pom檔案對專案進行描述, 在開發者在pom檔案中對專案屬性和構建過程進行定義後,maven即可自動構建專案並生成站點。
1.概覽
<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>
<!-- The Basics -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<dependencies>...</dependencies>
<parent>...</parent>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<!-- Build Settings -->
<build>...</build>
<reporting>...</reporting>
<!-- More Project Information -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- Environment Settings -->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
2.專案座標
宣告式的專案依賴管理,除了需要一個儲存有所有jar包的中央倉庫外,還需要一套合適的命名系統, 能夠保證每個jar包都有一個唯一的名字,包括不同版本。
在maven中,jar包的座標通過groupId
、artifactId
、version
三個欄位決定, 類似於xyz座標系的三個軸。
<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>org.codehaus.mojo</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
</project>
2.1 groupId
通常而言,一個組織(公司、基金會等)的專案通常使用同一個groupId, 因此一般而言groupId是該組織所擁有的域名,如jacoco對應的groupId為org.jacoco
, 而對應的網站為http://jacoco.org
(域名的優先順序順序是反過來的),
事實上不一定嚴格遵循該規則,如maven的groupId為org.apache.maven
, 而maven官方外掛的groupId為org.apache.maven.plugins
, 儘管同並不存在plugins.maven.apache.org
這個四級域名, 而且他們同屬於apache基金會(apache.org)下的專案。
另外也有完全和域名不相關的,如junit包的groupId為junt
。
另外需要注意的一點是,雖然groupId不必與專案的包結構相對應,但是遵循這種做法是一種最佳實踐, 因為這樣可以有效避免包之間的類衝突。
2.2 artifactId
artifactId通常是已知專案(或專案模組)的名稱,它與groupId一起構成了一個專案(或專案模組)的定位, 用於將該專案(或專案模組)與世界上其他所有專案(或專案模組)分開。
實踐
雖然在groupId中就可以看到專案名稱,但在artifactId中一般仍會將專案名稱作為開頭, 這是因為我們在溝通時通常會用 artifactId 進行交流,而不會帶上 groupId , 如張三告訴李四需要在專案中引入maven-enforcer-plugin
包做專案自動檢查。
2.3 version
如同字面的含義,version代表的是專案(或專案模組)的版本。
groupId
、artifactId
、version
一同定位了一個釋出包的唯一位置。 如maven-enforcer-plugin-3.0.0-M3.jar在倉庫中的位置為 $M2_REPO/org/apache/maven/plugins/maven-enforcer-plugin/3.0.0-M3
, 而其對應的pom中的內容如下:
<parent>
<groupId>org.apache.maven.enforcer</groupId>
<artifactId>enforcer</artifactId>
<version>3.0.0-M3</version>
</parent>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
parent標籤我們後續會提到,parent標籤下是父pom的座標,子pom繼承父pom的所有定義,包括version
3.package標籤
package標籤定義了構件的打包方式和使用方式,如果不指定值,預設使用jar作為打包方式。
這裡只說明兩個我們使用較多的打包方式:pom、jar。
3.1 打包為pom
定義打包方式為pom是有一些特殊的專案,其本身除了pom檔案外,不包含其他檔案, 這些專案中的pom主要是用來被其他專案引用、繼承,或者用來管理專案擁有的模組。
這裡的繼承概念與java中類的繼承概念類似,子pom擁有父pom的所有配置, 可以通過自定義的方式覆蓋/複寫父pom中已經存在的配置。
我們拿springboot整合中的兩個打包方式為pom的專案舉例說明。 在springboot的官方整合教程中,會告訴你可以使用兩種方式整合,一種通過父pom繼承:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
另一種是通過引入spring-boot的依賴管理:
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
如果我們找到spring-boot-starter-parent專案,就會發現這個專案裡僅僅只有pom檔案
這是因為在專案中,我們通常會存在多個子模組,而這些子模組會需要一些公用的配置, 比如統一的構建流程、統一的依賴版本管理,而這些內容就以pom的形式抽取出來, 形成了這種打包方式為pom的專案。
需要注意的是,打包方式為pom的專案裡,除了pom檔案,其他檔案不會參與構建。 事實上如果我們去看pom打包對應的預設外掛配置就會發現,只有install和deploy兩個生命週期有外掛繫結:
<phases>
<install>
org.apache.maven.plugins:maven-install-plugin:2.4:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
</deploy>
</phases>
3.2 打包為jar
jar包是java程式包常用的釋出形式,其可以作為庫檔案被其他專案使用, 也可以通過java -jar
命令直接執行。
jar包中即有靜態資源,也有編譯後生成的class檔案,這意味著其打包流程也更為複雜:
<phases>
<process-resources>
org.apache.maven.plugins:maven-resources-plugin:2.6:resources
</process-resources>
<compile>
org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
</compile>
<process-test-resources>
org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
</process-test-resources>
<test-compile>
org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
</test-compile>
<test>
org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
</test>
<package>
org.apache.maven.plugins:maven-jar-plugin:2.4:jar
</package>
<install>
org.apache.maven.plugins:maven-install-plugin:2.4:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
</deploy>
</phases>
3.2 其他
目前maven支援這些打包方式: pom、jar、maven-plugin、ejb、war、ear、rar。 其中每種打包方式都有相應的打包外掛對其進行打包,這些外掛都已經被maven預設進行了繫結, 具體打包方式對應的外掛配置有興趣的可以去 官網進行檢視。
4.專案間關係
在上文中我們提到了多種專案間關係,包含依賴關係、繼承關係、聚合關係(上文提到的模組管理)。
4.1依賴關係
專案間依賴關係是最常用的關係,當我們專案A使用一個專案B打成的jar時,A就對B形成了依賴。
需要注意的是,如果的java專案開發已經無法離開依賴了,A所依賴的專案B,實際上也開發中也依賴了專案C。 此時就生成的依賴的傳遞,即如果A依賴了B,B依賴了C,那麼A依賴C。
依賴傳遞的存在,會使得單點的依賴關係也變得複雜起來。實際在專案中,我們會依賴大量的jar包, 此時就可能存A依賴的B、C同時使用兩個不同版本的D:
A
|-B
|-D:1.1
|-C
|-D:1.2
我們知道,一個專案的不同版本通常會有大量的類是重複了,如果jvm在載入同名類時,會忽略重複的類, 這樣可能導致出現其中一個包只有一半程式碼的情況,這明顯是不合理的。這種依賴的出現就叫到依賴衝突, 當出現衝突時,maven會選取其中的一個版本放入最終的打包後文件中(如jar),而忽略其他版本。
具體的選取策略可以參考這篇短文
4.2繼承關係
繼承關係我們在上文中已經提到,我們可以通過parent標籤引入父級pom, 此時子pom擁有了父級pom中的所有定義,並可以在子pom中複寫相應的定義。
由於pom的繼承關係可能有多級,因此如果你想確認當前專案的最終pom結構時, 就必須要將所有的pom整合到一起,此時我們可以通過mvn help:effective-pom
命令生成整合後的pom (如果使用idea,右鍵maven選單下也有相應的生成操作)。
如果在一個沒有父pom的專案中執行mvn help:effective-pom
,你會發現最終生成的pom中, 突然出現了大量你沒有定義的內容,這是因此maven自身定義了一個super pom, 所有的pom都預設繼承該pom(可類比java中的Object類), 在上文中我們提到的maven專案的預設結構,就是在這裡定義的。
super pom的具體內容,可以去官網檢視
子pom並不會繼承父pom的所有內容,僅以下內容將會被繼承: - groupId - version - scm - issueManagement - ciManagement - mailingLists - properties - dependencyManagement - dependencies - repositories - pluginRepositories - build - reporting - profiles - description - url - inceptionYear - organization - licenses - developers - contributors
需要注意的是以下幾個標籤不會被繼承: - artifactId - package - name - prerequisites
4.3聚合關係
聚合關係常常出現在有多個模組的專案中,比如專案A有三個子專案aa、ab、ac, 這時我們可以在目錄A下也新增一個pom,並定義aa、ab、ac是專案A的三個子專案, 這樣我們對專案A做構建(如mvn clean package)時,maven就會分別對aa、ab、ac做相應的構建操作。
A
|-- pom.xml
|-- aa
|-- pom.xml
|-- ab
|-- pom.xml
|-- ac
|-- pom.xml
子專案的宣告方式以通過modules標籤進行,當我們在專案A的pom中新增如下配置, aa、ab、ac就會成為A的子專案:
<modules>
<module>aa</module>
<module>ab</module>
<module>ac</module>
</modules>
aa、ab、ac並不是這三個專案的相對路徑(相對於A)
在這裡,可能有的人已經注意到了,這種專案層級結構,非常適合將A專案的pom作為父pom, 將aa、ab、ac的pom中相同的內容抽取至父pom中進行管理。
實際上在專案的實踐中,通常也是這麼處理的,但也有一些例外。比如aa、ab是springboot專案, 而ac卻是老舊的spring專案,此時可能出現:A作為aa和ab的父級pom統一管理相關配置,
同時aa和ab作為子專案存在,而ac不繼承A,只作為子專案存在,這樣我們即能夠統一管理aa和ab的配置, 也能夠通過一條命令,對aa、ab、ac一起完成構建操作。
(實際實踐過程中,不推薦這種模式,我們可以把ac提到與A平級的位置,這樣的結構更清晰)
值得一提的是,如果aa與ab間存在依賴關係,比如aa依賴於ab,那麼即使aa比ab先宣告,maven在構建時, 也會很聰明的先對ab進行構建,再構建aa。
5.dependencies標籤
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<type>jar</type>
<scope>test</scope>
<optional>true</optional>
</dependency>
</dependencies>
dependencies標籤下聲明瞭當前專案所使用的依賴構件。
需要注意的是,這裡宣告的檔案必須能夠在maven倉庫中找到, maven預設使用中央倉庫,中央倉庫中包括了所有常用的開源jar包, 但對於一些閉源的三方jar包,就無法在中央倉庫中找到。
因此,公司一般都會有自己的私有倉庫,我們需要修改maven的settings.xml檔案, 使maven使用私服處理依賴和釋出。
對於私有倉庫上沒有的專案,我們可能通過以下命令,將其上傳至私有倉庫:
shell script mvn deploy:deploy-file -Dfile=non-maven-proj.jar -DgroupId=some.group \
-DartifactId=non-maven-proj -Dversion=1.0 -Dpackaging=jar
5.1 classifier標籤
在引用依賴時,僅僅使用groupId
、artifactId
、version
作為座標可能還並不太足夠。
考慮一下這種場景:有一個專案,該專案需要提供一個基於jre1.7的jar包, 同時還提供了一個基於jre1.8的jar包,即一套原始碼需要使用兩種jdk打成不同的jar包。
這種情況下我們會配置兩套打包方式,預設使用1.8打包,並可選擇1.7打包,配置如下:
<profile>
<id>jdk17</id>
<properties>
<!--使用jdk1.7打包-->
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<!--配置classifier,與1.8版本的包區分-->
<classifier>jdk17</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
如果用1.8打包釋出的jar名稱為demo-1.0.jar
,那麼用1.7打包時, 釋出的jar的名稱為demo-1.0-jdk17.jar
——實際場景下,classifier標籤可以配置成任意字串, 並不一定是jdk17。
而使用者在引入1.7版本的依賴時,就需要額外新增classifier標籤。
<dependency>
<groupId>some.example</groupId>
<artifactId>demo</artifactId>
<version>1.0</version>
<classifier>jdk17</classifier>
</dependency>
classifier標籤有一處更常見的地方,是sources.jar
:
這就是我們上文中說到的一個pom,打出了兩個包,原始碼包對應的classifier標籤就是sources
, 而測試原始碼包對應的值為test-sources
。
5.2 type標籤
type標籤描述了依賴型別,如果不定義該值,使用預設值jar。
對於不同型別的依賴,maven的處理策略也不相同:
type | classifier | extension | packaging | language | added to classpath | includesDependencies |
---|---|---|---|---|---|---|
pom | = type | = type | none | |||
jar | = type | = type | java | true | ||
test-jar | tests | jar | jar | java | true | |
maven-plugin | jar | = type | java | true | ||
ejb | jar | = type | java | true | ||
ejb-client | client | jar | ejb | java | true | |
war | = type | = type | java | true | ||
ear | = type | = type | java | true | ||
rar | = type | = type | java | true | ||
java-source | sources | jar | = type | java | ||
javadoc | javadoc | jar | = type | java | true |
實際開發中,我們只會用到jar
和pom
兩種型別,大家可以注意到pom
型別的依賴是不會產生依賴傳遞,
也不會被加入classpath中。
5.3 scope標籤
該標籤定義了該包在哪些階段會被放到classpath中,另外還定義了依賴的可傳遞性。其聚會範圍如下:
classpath即類、類庫載入路徑,在我們的依賴中,大多數依賴會在編譯、測試、執行都用到, 但也有一些依賴可能只有在編譯、測試階段運到(比如測試相關的包),還有部分包可能在編譯、測試時需要, 但實際釋出時不需要(比如jdk相關的包)。
值 | 含義 | 是否會將該依賴向下傳遞 |
---|---|---|
compile | 該值是預設值。該依賴在所有階段的都會放入classpath中,是最常用的值。 | 是 |
provided | 只有在編譯和測試時放入classpath中,實際打包時忽略該包 | 否 |
runtime | 編譯時不需要,只在測試和打包時需要 | 是 |
test | 只在對測試程式碼做編譯和執行時需要 | 否 |
system | 手動指定所依賴包的本地存在路徑 | 只傳遞本身 |
- compile是最常用的,一般專案引入的包在編譯、測試、釋出階段都會用到。
- provided一般用於基礎包,在基礎包的開發過程中會依賴一些三方包, 但可能不想為使用者帶來依賴衝突的負擔,此時可以使用該範圍,由使用者自行決定該三方包的版本
(當然需要是相容的版本);另一種不推薦的使用方式是將該依賴作為可選依賴向下傳遞, 舉一個例子:我們開發了一個基礎工具類包,裡面含有許多工具,像poi工具、時間轉換工具、中文數字工具,
另外裡面還包含了TapConfigUtil用來獲取配置, 此時我們注意到配置中心的整合是以springboot為基礎的, 而我們想讓該工具包也可以被其他型別的專案使用,
這種情況下我們可以給
commons-aap-appconfig
包加上provided範圍。 如果某個使用者只不需要使用TapConfigUtil,那麼他就不需要引入commons-aap-appconfig
包。 (事實上commons-aap-appconfig
包裡引入spring相關的依賴裡使用了provided範圍) - runtime很少用到,一般情況下使用compile就可以了, 使用runtime甚至可能給IDE帶來一些問題
- test用在測試相關的包上,測試程式碼的安全性會更低(畢竟沒人會寫測試程式碼來測試測試程式碼 ——對外發布的測試工具除外),使用test標記測試相關的包,可以避免被其他開發人員錯誤的使用。
- system需要配置
systemPath
標籤使用,用來指定使用倉庫中沒有的包。 與其相比,上傳私服是一種更好的方式,因為system標記的包,並不會將其依賴的包傳遞給使用者, 這意味著你需要自己處理好依賴關係。
5.4 systemPath標籤
僅在依賴項範圍是system時使用,否則構建將失敗。
systemPath標籤中的路徑必須是絕對路徑,因此建議使用maven變數拼裝路徑, 例如${project.basedir}/lib。
5.5 optional標籤
在上面介紹scope標籤的provided取值時,我們講了它可以實現讓使用都按需決定是否引入依賴, 這樣以達到避免引入不需要的依賴。
事實上provided更多的是用來要求使用方或執行時系統提供具體的包,而不是用於這種用途。
配置依賴的可選應該使用optional標籤來實現。
5.6 exclusions標籤
dependency標籤下還存在exclusions標籤,我們在前面講到了依賴傳遞可能帶來的依賴衝突問題, exclusions標籤就是排除掉不想被傳遞過來的依賴。
這裡需要注意的是,A->B->C時,如果你在引入依賴A的時候,將B排除,那麼C也將被排除, 除非有其他地方也引入了C(有點像對“樹”進行操作,排除進相當於直接剪掉了一個“子樹”)
<exclusions>
<exclusion>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
</exclusion>
</exclusions>
exclusion下只有groupId與artifactId兩個標籤,因為只不可能同時依賴一個包的不同版本。
5.7 其他
version標籤除了指定具體版本外,還可以指定範圍版本,有興趣的可以檢視 官網, 不過不推薦在專案中使用。
各小版本在maven專案中的先後釋出順序可以看 這裡
6.dependencyManagement標籤
正如其名字所代表的含義,該標籤的作用就是管理依賴。
在上面的dependency標籤的介紹中,我們看到在dependency標籤下有眾多的子標籤, 想象一下我們有多個服務,如果每個服務都在引入同一依賴時配置好所有內容,
那將是非常麻煩,而且如果需要對某個所依賴的jar包做升級時,我們不得不改每一個pom中的version資訊。
可能有人已經想到,我們可以將這些依賴放到父pom裡面就可以了。這是一種很好的策略, 但讓我們考慮專案A、B,A中引入了a、b三個包,B中引入b、c兩個包, 如果我們使用父pom管理公用依賴,將只有b會被放到父pom裡,而a、c由A、B自己管理。 如果後來B專案的功能增加,也需要引入a包,那麼我們就需要把a包從A專案中提到父pom中, 以保證對依賴的統一管理——因為,這種方式並不能真正實現依賴的統一管理。
現在考慮另一種方式,我們在父pom裡列出專案中所有使用到的依賴包,並配置好所有內容, 但並不真正的對這些包產生依賴,僅僅是列出來。如果那個子pom真正需要用到, 那麼他只需要宣告一些儘可能少的資訊,就可以對對應的包產生依賴 ——這種方案就是使用dependencyManagement標籤。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.demo.company</groupId>
<artifactId>someproject-commons-versions</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
dependencyManagement標籤下只有一個dependencies標籤, 裡面的內容和上文中的dependencies標籤幾乎一樣,唯一不一樣的是, 在dependencyManagement標籤下的scope標籤新增一種取值——import。
import只能用於匯入package值為pom的依賴包, 當在專案A的pom中使用import匯入專案B時,A、B的dependencyManagement標籤下的內容將被合併。 這是一個非常有用的特性,這意味著我們可以建立一個專案專門用來做統一版本控制, 在這個專案裡,我們將所有的包的version、scope等內容都定義好, 其他專案只需要在dependencyManagement下匯入該包,而不需要破壞其本身的繼承結構。
雖然dependencyManagement有這樣的方便, 但需要注意的是dependencyManagement下的內容不僅對當前pom、子pom生效, 同樣會覆蓋由於依賴傳遞而引入的jar包。比如在專案A中,我們依賴了aa-1.0、bb-2.0, 而在bb-2.0中依賴了aa-2.0,此時,如果aa的版本是在dependencyManagement中定義, 那麼aa-2.0將被放棄,aa-1.0生效,這樣將導致bb-2.0可能使用了根本不存在的類和api, 最終專案構建失敗。
7.Properties標籤
如其字面意思——屬性。properties標籤下定性的屬性,可以在pom的任何地方被引用, 引用方式為${屬性名}
,其中屬性名即標籤名,我們可以利用這些屬性管理一組依賴, 或者將一些容易變化的值集中到properties標籤下管理。
<properties>
<org.powermock.version>2.0.2</org.powermock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>${org.powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${org.powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${org.powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
除了自定義屬性外,maven外掛也會提供一些屬性,以達到對其進行配置的目的。 同時,maven還提供以下屬性可以在pom檔案中使用: - env.X : 以字首env
開頭的屬性,對應於系統環境變數, 比如通過${env.PATH}
可以取到環境變數PATH
的值。這裡需要注意的是, maven對環境變數的大小寫是敏感的,windows下的環境變數在maven中必須全部大寫。 - project.X : project
為字首是屬性是專案的屬性值, 包括了pom中的標籤——比如${project.parent.artifactId}可以獲取 *<parent>* 下 *<artifactId>* 的值;除此之外還有一些其他屬性,具體可參考 [官網說明](http://maven.apache.org/ref/3.6.3/maven-model-builder/#Model_Interpolation)。 值得一提是,該屬性值並不是取當前pom中的標籤,而是取**effective-pom**中的標籤, 也就是說可以取到從父pom中繼承的一些內容。 - settings.X : 與**project.X**類似,還過**settings.X**取的是**settings.xml**中的內容, 具體內容同樣參考 [官網說明](http://maven.apache.org/ref/3.6.3/maven-model-builder/#Model_Interpolation)。 - java系統屬性 : 所有可以通過`java.lang.System.getProperties()`方法獲取的變數, 均可以在pom中獲取,如**${java.home}**。具體可參考 oracle官方文件
三、配置構建任務
在上一章的內容是pom的一些基礎標籤的使用,靈活使用這些基礎標籤就可以達成專案的基本構建任務。
除了基本構建外,我們還往往在構建時需要進行打包前依賴衝突檢測、釋出前進碼檢查、 釋出時同步釋出原始碼包、自動生成並部署專案站點等任務,maven作為強大的構建工具, 通過構建外掛的方式,將所有的這些任務全部沿著maven構建生命週期自動執行。
1.maven構建的生命週期
maven構建的生命週期定義了打包和釋出專案時需要經歷的流程,maven內建的生命週期有 clean(清理)、default(打包釋出)、site(生成站點文件)三種。
這三種生命週期由一些階段(phase)組成,三種生命週期對應的階段如下:
1.1 clean生命週期
Phase | Description |
---|---|
pre-clean | execute processes needed prior to the actual project cleaning |
clean | remove all files generated by the previous build |
post-clean | execute processes needed to finalize the project cleaning |
1.2 default生命週期
Phase | Description |
---|---|
validate | validate the project is correct and all necessary information is available. |
initialize | initialize build state, e.g. set properties or create directories. |
generate-sources | generate any source code for inclusion in compilation. |
process-sources | process the source code, for example to filter any values. |
generate-resources | generate resources for inclusion in the package. |
process-resources | copy and process the resources into the destination directory, ready for packaging. |
compile | compile the source code of the project. |
process-classes | post-process the generated files from compilation, for example to do bytecode enhancement on Java classes. |
generate-test-sources | generate any test source code for inclusion in compilation. |
process-test-sources | process the test source code, for example to filter any values. |
generate-test-resources | create resources for testing. |
process-test-resources | copy and process the resources into the test destination directory. |
test-compile | compile the test source code into the test destination directory |
process-test-classes | post-process the generated files from test compilation, for example to do bytecode enhancement on Java classes. |
test | run tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed. |
prepare-package | perform any operations necessary to prepare a package before the actual packaging. This often results in an unpacked, processed version of the package. |
package | take the compiled code and package it in its distributable format, such as a JAR. |
pre-integration-test | perform actions required before integration tests are executed. This may involve things such as setting up the required environment. |
integration-test | process and deploy the package if necessary into an environment where integration tests can be run. |
post-integration-test | perform actions required after integration tests have been executed. This may including cleaning up the environment. |
verify | run any checks to verify the package is valid and meets quality criteria. |
install | install the package into the local repository, for use as a dependency in other projects locally. |
deploy | done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects. |
1.3 site生命週期
Phase | Description |
---|---|
pre-site | execute processes needed prior to the actual project site generation |
site | generate the project's site documentation |
post-site | execute processes needed to finalize the site generation, and to prepare for site deployment |
site-deploy | deploy the generated site documentation to the specified web server |
每個生命週期擁有的階段互不重疊,且在構建時每個生命週期中的階段按順序進行。 這意味著當我們使用mvn
命令構建時,如果指定了某一階段,maven會先執行所有前置階段, 再執行指定的階段——這使得maven的使用相當容易,使用都只需要記住常用的幾個階段 (如clean、deploy、site)即可單獨完成專案的構建和釋出。
2.外掛的“構建階段”
除了maven內建的階段外,maven允許外掛自定義階段,比如maven-dependency-plugin 外掛中常用的copy-dependencies階段。外掛階段與內建階段一樣可以使用mvn
命令執行, 唯一不同的是需要在階段前加上外掛的座標資訊,如mvn dependency:copy-dependencies
(使用非maven官方外掛時,需要將dependency換成groupId:artifactId的形式)。
在使用這些外掛前,我們需要在pom檔案中引入外掛:
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
</plugins>
</build>
事實上基本構建外掛maven都有內建,super pom中甚至進行了缺少配置,不需要我們再手動新增
雖然maven允許外掛自定義階段,但不允許修改內建的生命週期,
3.build標籤
build標籤下定義了構建的相關資訊和配置:
<build>
<defaultGoal>install</defaultGoal>
<directory>${basedir}/target</directory>
<finalName>${artifactId}-${version}</finalName>
<resources>
<resource>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<targetPath>META-INF/plexus</targetPath>
<filtering>false</filtering>
<directory>${basedir}/src/main/plexus</directory>
<includes>
<include>configuration.xml</include>
</includes>
<excludes>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>
<testResources>
...
</testResources>
<filters>
<filter>filters/filter1.properties</filter>
</filters>
</build>
3.1 defaultGoal
預設構建目標(階段),如果配置該值,在執行mvn
命令時可以不指定構建階段。
<defaultGoal>clean package</defaultGoal>
3.2 directory
構建生成的相關檔案的存放位置,預設配置為${project.basedir}/target
。
3.3 finalName
構建生成jar包(或其他型別的包)的名稱,預設為${artifactId}-${version}
。
需要注意的是,finalName並非如字面意思一般是打包後的最終名稱, 實際打包外掛可能會在後面新增字尾:如果我們配置finalName配置為demo, 同時為打包外掛配置classifier標籤為jdk17(上面我們講到過), 那麼實際生成的jar名稱為demo-jdk17.jar。
3.4 resources 與 testResources
resources標籤用來指定資原始檔的存放路徑,這些檔案不會參與編譯,會直接拷貝到相應的路徑下。
- targetPath: 拷貝目標路徑,在default週期的process-resources階段,資源將被拷貝到對應的路徑下。 如果不指定,檔案將被拷貝至根目錄下。
- filtering: 是否啟用過濾,如果啟用,則允許在resource指定的檔案中使用佔位符替換 ,預設不啟用。這裡的替換範圍包括所有在pom中可以使用的變數,替換方式為
${placeholder}
——這是官方文件的說法,實際在spring boot專案中的有效使用格式為@placeholder@
- directory: 資原始檔的源路徑,預設為
${project.basedir}/src/main/resources
- includes: 源路徑下需要包含的檔案,允許使用萬用字元的方式匹配
- excludes: 源路徑下需要排除的檔案,允許使用萬用字元的方式匹配
testResources中的內容與resources一致,不同的是其對應的生命週期和directory的預設值。
3.5 filter
filter可以用來引入一些properties檔案,在這樣檔案裡定義的屬性, 也會加入到開啟了filtering的資原始檔的佔位符替換範圍。
3.6 plugins 與 pluginManagement
3.6.1 plugins
plugins標籤用於引入和配置打包外掛。
<build>
...
<pluginManagement>
<plugins>
...
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<extensions>false</extensions>
<inherited>true</inherited>
<configuration>
<classifier>test</classifier>
</configuration>
<dependencies>...</dependencies>
<executions>...</executions>
</plugin>
</plugins>
</build>
- extensions: 是否載入該外掛擁有的擴充套件包(後方會講到),預設為false
- inherited: 是否允許子級pom繼承,預設為true
- configuration: configuration下的內容較為特殊,maven並未約束其子級標籤的範圍。 實際上子標籤對應是的外掛的可配置屬性,因此configuration下的內容需要通過閱讀外掛的使用文件來進行配置 這裡需要注意的是,無論configuration出現在父級pom還是在pluginManagement中, 其中的定義屬性最終會在合併後傳遞給外掛。 (合併策略參考官方網站)
- dependencies: 與專案的dependencies結構一致,不同的是這些依賴不作用於專案,而是作用的外掛, 通過指定依賴,我們可以用來指定外掛依賴的包的版本等資訊。
- executions: 對外掛提供的maven階段進行配置,該標籤下的內容較為複雜,實際使用也比較多,值得單獨一講
executions下包含的是execution列表,每個execution可以理解為一個任務。
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<id>echodir</id>
<goals>
<goal>run</goal>
</goals>
<phase>verify</phase>
<inherited>false</inherited>
<configuration>
<tasks>
<echo>Build Dir: ${project.build.directory}</echo>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
- id: 任務的id,用來區分不同的execution,該值會在該任務執行時打印出來, 格式:[plugin:goal (id)]
- goals: 需要外掛執行的構建目標(階段),每個外掛具體提供的構建目標可以參考官方文件, 或者通過maven-help-plugin檢視外掛的資訊:
mvn help:describe -Dplugin=com.github.spotbugs:spotbugs-maven-plugin:3.1.12.2
- phase: 指定需要繫結的maven內建構建階段。在這裡需要注意,外掛的構建階段為goal, 內建構建階段為phase,因為外掛的階段是沒有生命週期的。phase標籤的作用, 就是將execution對應的任務繫結到maven的某個內建構建階段,在maven生命週期時, 當執行到該phase時,會自動執行該execution下的goals,從而對maven構建進行擴充套件。 比如我們可以將pmd外掛的
pmd、check
關聯到verfiy
階段,這樣當我們執行mvn verify
時, 會自動進行程式碼問題的檢查。 - inherited: 是否允許該execution被子pom繼承,預設為true
- configuration: 與外層的configuration內容一致,不過僅對該execution生效
3.6.2 pluginManagement
pluginManagement的作用與dependencyManagement的作用類似,我們可以在父pom裡配置所有外掛, 但不實際引入外掛,子pom按需在plugins標籤下引入具體外掛,這樣就可以達到外掛配置集中管理的效果 —— 同dependencies下引入dependencyManagement中的依賴一樣, plugins下引入pluginManagement中的外掛只需要宣告groupId與artifactId。
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<id>echodir</id>
<goals>
<goal>run</goal>
</goals>
<phase>verify</phase>
<inherited>false</inherited>
<configuration>
<tasks>
<echo>Build Dir: ${project.build.directory}</echo>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
</plugin>
</plugins>
</build>
4.profiles標籤
profiles允許我們定義多個profile,並設定每個profile的啟用條件,以達到自如切換多套配置的目的。
<profiles>
<profile>
<id>test</id>
<activation>...</activation>
<build>...</build>
<modules>...</modules>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<dependencies>...</dependencies>
<reporting>...</reporting>
<dependencyManagement>...</dependencyManagement>
<distributionManagement>...</distributionManagement>
</profile>
</profiles>
4.1 id
如字面意思,即套profile的標識,我們可以通過mvn
命令的-P
選項直接通過id啟用對應的profile: mvn -P test clean deploy
4.2 activation
profile的自動啟用條件。
除了通過手動指定profile外,還可以根據一些條件,自動啟用profile。
<profile>
<id>test</id>
<activation>
<activeByDefault>false</activeByDefault>
<jdk>1.5</jdk>
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
<property>
<name>sparrow-type</name>
<value>African</value>
</property>
<file>
<exists>${basedir}/file2.properties</exists>
<missing>${basedir}/file1.properties</missing>
</file>
</activation>
</profile>
其中activeByDefault表示是否為預設啟用,預設值為false。如果配置為true, 當沒有配置檔案被啟用時,啟用該配置檔案
其他的標籤是一些具體啟用條件,在maven3.2.2版本只要滿足一項啟用條件就成立(或關係), 在maven3.2.2以後必須滿足所有項啟用條件才成立(與關係): - jdk: 指定構建時的jdk版本,除了指定具體版本外,也可以指定範圍 - os: 作業系統資訊,具體約束條件內容可檢視這裡 - property: 將該屬性的key-value滿足條件時該項成立。這裡的屬性的範圍與pom檔案一致。 - file: 檔案存在或缺失,配置路徑須為絕對路徑。該項需要注意的是,file標籤下的屬性替換, 只允許出現basedir、系統變數、maven命令傳遞變數這三種。
在我們配置好profiles,如果想知道在當前環境下哪個配置將被啟用, 可以通過mvn help:active-profiles
檢視。
4.3其他標籤
modules、dependencies、dependencyManagement標籤我們在前文中已經講過, profile下這些標籤的可配置內容與其在根標籤(project)下的一致, profile下的配置可以覆蓋根標籤下的配置。
repositories、pluginRepositories、reporting、distributionManagement 這些標籤在根標籤下也存在,只是我們還沒有聊到,與上面的這些標籤一樣, 這些標籤的可配置內容與其在根標籤下的一致,profile下的配置可以覆蓋根標籤下的配置。
build標籤較為特殊,其在profile下的可配置內容只包含前文中我們提到的所有內容, 但在根標籤下的build標籤還有一些獨佔內容我們沒有提到。
5.project標籤下的build標籤
在project標籤下的build標籤下還有幾個標籤,這些標籤僅出現在project標籤下的build中, 無法在profile中的內容配置。
這些標籤包括目錄結構標籤——sourceDirectory、sourceDirectory、testSourceDirectory、 outputDirectory、testOutputDirectory,以及extensions標籤。
5.1 *Directory標籤
這些標籤定義了專案的目錄結構,標籤的含義和其英文名字一致,標籤的值必須為絕對路徑 (一般使用`${project.basedir}進行拼接)。
這樣需要注意的是除了上述的幾個標籤外,我們還可以找到一個scriptSourceDirectory標籤, 該標籤實際沒有用處,已經被官宣廢棄。
5.2 extensions標籤
extensions下可以引入一些jar對maven能力進行擴充套件,比如引入wagon-ssh新增對ssh協議的支援, 與plugins中的外掛不同,擴充套件程式不需要提供構建目標(gloas),只是提供能力基礎, 因此需要另行引入外掛配合使用。
我們舉一個利用ssh協議進行包自動部署的例子:
<build>
<extensions>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>2.10</version>
</extensions>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<serverId>devServerRoot</serverId>
<formFile>target/demo.jar</formFile>
<url>scp://[email protected]:/root/applications/demo</url>
<commands>
<command>/root/applications/demo/restart.sh</command>
</commands>
<desplayCommandOutputs>true</desplayCommandOutputs>
</configuration>
</plugin>
</plugins>
</build>
其中serverId中的的devServerRoot於server標籤中的id, server標籤下可以定義伺服器的賬號密碼資訊,一般在settings.xml中配置,也可以在pom中配置。
<server>
<id>devServerRoot</id>
<username>root</username>
<password>password</password>
</server>
5.Reporting標籤
reporting下的內容與site
生命週期相關,reporting標籤下可以定義一些外掛用於生成報告文件, 比如javadoc。
與build類似,reporting也提供了外掛的配置能力,與build不同的是, reporting下的外掛無法被繫結到指定的maven階段(預設繫結site
階段)
<reporting>
<plugins>
<excludeDefaults>false</excludeDefaults>
<outputDirectory>${basedir}/target/site</outputDirectory>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<reportSets>
<reportSet>
<id>sunlink</id>
<reports>
<report>javadoc</report>
</reports>