使用maven-pom進行依賴管理與自動構建html
最後編輯於 :pencil::{docsify-updated}
java
隨着開發生態環境的不斷髮展,幾乎全部的java應用都會使用第三方的類庫,尤爲是在這個開源的世界裏, java應用依賴管理已經很難再由人工完成——依賴管理包括解決依賴傳遞、版本衝突、依賴臃腫等問題。git
maven經過groupId
、artifactId
、version
造成的座標定位系統能準確的定位每個構件(artifact), 開發者能夠經過在pom文件中列出所依賴的構件的座標,讓maven工具從maven倉庫中自動下載全部須要的構件; 另外一方面,經過pom文件間的依賴傳遞、繼承等方式下降依賴管理的難度。github
在咱們的開發過程當中,除了編寫代碼之外,有很大一部分時間是花在編譯、運行單元測試、生成文檔、打包和部署這些工具上, 爲也提升工做效率,使開發人員能更多的將精力用於開發工做,咱們須要像maven這樣的工具, 如流水線般的將全部部署以自動化的方式完成。web
在java開發中,經常使用的構建工具備Ant、maven、gradle三種。spring
Ant是過程式的,開發者須要顯示的指定每一個目標,以及完成該目標鎖須要執行的任務。shell
Ant不只限於對java項目進行構建,也能夠對其餘語言(如C語言)的項目進行構建。apache
<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支持。windows
ant的這種聲明方式,使開發者能夠根據本身的須要靈活配置,定製本身的項目構建流程; 另外一方面,這種靈活的配置方式,也使得ant的配置相對繁瑣,可讀性較差,學習成本也更高 ——事實上,ant的官方網站上列出的插件就有139種,並且部分工具的官網已經404了。api
maven與gradle同樣,都是聲明式的配置方式,相對於Ant來講,配置更加方便。 同時maven有內建生命週期,約定了項目的代碼結構,只須簡單配置,就能夠完成構建任務。
頂級pom中定義的項目結構(即默認項目結構)
項目根目錄 |-- src | |-- main | | |-- java -> 主代碼文件 | | |-- resources -> 主資源文件 | | -- scripts -> 雖然在maven的默認結構裏有這個目錄,但官網文檔中已刪除了這一級目錄,不建議使用 | -- test | |-- java -> 測試代碼文件 | -- resources -> 測試資源文件 -- pom.xml
小提示:可經過mvn -DarchetypeCatalog=internal archetype:generate
初始化標準結構的maven項目
另外一方面,maven內建生命週期,使用沒法更加項目的構建順序,若是想在構建中加上一些其餘處理邏輯, 則須要用編寫maven插件的方式來完成,成本相對高昂。
gradle是相對新穎的構建工具,它使用一種基於Groovy的特定領域語言(DSL)來聲明項目設置, 目前也增長了基於Kotlin語言的kotlin-based DSL,拋棄了基於XML的各類繁瑣配置。
gradle沒有maven般的生命週期,真正起做用的是所引入的Plugin,所以相對更爲靈活。
另外一方面,gradle做爲新生工具,不管是程序仍是文檔都在不斷的完善中,使用和學習相對困難。 同時,因爲gradle的配置是基於groovy/kotlin語法的,所以使用gradle須要掌握相應的語法知識。
gradle是對ant和maven特色折衷的結果,相對於ant其可讀性更好,相對於maven其更爲靈活。
POM是project object mode的簡寫,maven經過pom文件對項目進行描述, 在開發者在pom文件中對項目屬性和構建過程進行定義後,maven便可自動構建項目並生成站點。
<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>
聲明式的項目依賴管理,除了須要一個存儲有全部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>
一般而言,一個組織(公司、基金會等)的項目一般使用同一個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沒必要與項目的包結構相對應,可是遵循這種作法是一種最佳實踐, 由於這樣能夠有效避免包之間的類衝突。
artifactId一般是已知項目(或項目模塊)的名稱,它與groupId一塊兒構成了一個項目(或項目模塊)的定位, 用於將該項目(或項目模塊)與世界上其餘全部項目(或項目模塊)分開。
實踐
雖然在groupId中就能夠看到項目名稱,但在artifactId中通常仍會將項目名稱做爲開頭, 這是由於咱們在溝通時一般會用 artifactId 進行交流,而不會帶上 groupId , 如張三告訴李四須要在項目中引入maven-enforcer-plugin
包作項目自動檢查。
如同字面的含義,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
package標籤定義了構件的打包方式和使用方式,若是不指定值,默認使用jar做爲打包方式。
這裏只說明兩個咱們使用較多的打包方式:pom、jar。
定義打包方式爲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>
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>
目前maven支持這些打包方式: pom、jar、maven-plugin、ejb、war、ear、rar。 其中每種打包方式都有相應的打包插件對其進行打包,這些插件都已經被maven默認進行了綁定, 具體打包方式對應的插件配置有興趣的能夠去 官網進行查看。
在上文中咱們提到了多種項目間關係,包含依賴關係、繼承關係、聚合關係(上文提到的模塊管理)。
項目間依賴關係是最經常使用的關係,當咱們項目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),而忽略其餘版本。
具體的選取策略能夠參考這篇短文
繼承關係咱們在上文中已經提到,咱們能夠經過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
聚合關係經常出如今有多個模塊的項目中,好比項目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。
<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
在引用依賴時,僅僅使用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
。
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中。
該標籤訂義了該包在哪些階段會被放到classpath中,另外還定義了依賴的可傳遞性。其聚會範圍以下:
classpath即類、類庫加載路徑,在咱們的依賴中,大多數依賴會在編譯、測試、運行都用到, 但也有一些依賴可能只有在編譯、測試階段運到(好比測試相關的包),還有部分包可能在編譯、測試時須要, 但實際發佈時不須要(好比jdk相關的包)。
值 | 含義 | 是否會將該依賴向下傳遞 |
---|---|---|
compile | 該值是默認值。該依賴在全部階段的都會放入classpath中,是最經常使用的值。 | 是 |
provided | 只有在編譯和測試時放入classpath中,實際打包時忽略該包 | 否 |
runtime | 編譯時不須要,只在測試和打包時須要 | 是 |
test | 只在對測試代碼作編譯和執行時須要 | 否 |
system | 手動指定所依賴包的本地存在路徑 | 只傳遞自己 |
commons-aap-appconfig
包加上provided範圍。 若是某個使用者只不須要使用TapConfigUtil,那麼他就不須要引入commons-aap-appconfig
包。 (事實上commons-aap-appconfig
包裏引入spring相關的依賴裏使用了provided範圍) systemPath
標籤使用,用來指定使用倉庫中沒有的包。 與其相比,上傳私服是一種更好的方式,由於system標記的包,並不會將其依賴的包傳遞給使用者, 這意味着你須要本身處理好依賴關係。 僅在依賴項範圍是system時使用,不然構建將失敗。
systemPath標籤中的路徑必須是絕對路徑,所以建議使用maven變量拼裝路徑, 例如${project.basedir}/lib。
在上面介紹scope標籤的provided取值時,咱們講了它能夠實現讓使用都按需決定是否引入依賴, 這樣以達到避免引入不須要的依賴。
事實上provided更多的是用來要求使用方或運行時系統提供具體的包,而不是用於這種用途。
配置依賴的可選應該使用optional標籤來實現。
dependency標籤下還存在exclusions標籤,咱們在前面講到了依賴傳遞可能帶來的依賴衝突問題, exclusions標籤就是排除掉不想被傳遞過來的依賴。
這裏須要注意的是,A->B->C時,若是你在引入依賴A的時候,將B排除,那麼C也將被排除, 除非有其餘地方也引入了C(有點像對「樹」進行操做,排除進至關於直接剪掉了一個「子樹」)
<exclusions> <exclusion> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> </exclusion> </exclusions>
exclusion下只有groupId與artifactId兩個標籤,由於只不可能同時依賴一個包的不一樣版本。
version標籤除了指定具體版本外,還能夠指定範圍版本,有興趣的能夠查看 官網, 不過不推薦在項目中使用。
各小版本在maven項目中的前後發佈順序能夠看 這裏
正如其名字所表明的含義,該標籤的做用就是管理依賴。
在上面的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, 最終項目構建失敗。
如其字面意思——屬性。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構建生命週期自動執行。
maven構建的生命週期定義了打包和發佈項目時須要經歷的流程,maven內置的生命週期有 clean(清理)、default(打包發佈)、site(生成站點文檔)三種。
這三種生命週期由一些階段(phase)組成,三種生命週期對應的階段以下:
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 |
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. |
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)便可單獨完成項目的構建和發佈。
除了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容許插件自定義階段,但不容許修改內置的生命週期,
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>
默認構建目標(階段),若是配置該值,在執行mvn
命令時能夠不指定構建階段。
<defaultGoal>clean package</defaultGoal>
構建生成的相關文件的存放位置,默認配置爲${project.basedir}/target
。
構建生成jar包(或其餘類型的包)的名稱,默認爲${artifactId}-${version}
。
須要注意的是,finalName並不是如字面意思通常是打包後的最終名稱, 實際打包插件可能會在後面添加後綴:若是咱們配置finalName配置爲demo, 同時爲打包插件配置classifier標籤爲jdk17(上面咱們講到過), 那麼實際生成的jar名稱爲demo-jdk17.jar。
resources標籤用來指定資源文件的存放路徑,這些文件不會參與編譯,會直接拷貝到相應的路徑下。
${placeholder}
——這是官方文檔的說法,實際在spring boot項目中的有效使用格式爲@placeholder@
${project.basedir}/src/main/resources
testResources中的內容與resources一致,不一樣的是其對應的生命週期和directory的默認值。
filter能夠用來引入一些properties文件,在這樣文件裏定義的屬性, 也會加入到開啓了filtering的資源文件的佔位符替換範圍。
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>
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>
mvn help:describe -Dplugin=com.github.spotbugs:spotbugs-maven-plugin:3.1.12.2
pmd、check
關聯到verfiy
階段,這樣當咱們運行mvn verify
時, 會自動進行代碼問題的檢查。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>
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>
如字面意思,即套profile的標識,咱們能夠經過mvn
命令的-P
選項直接經過id激活對應的profile: mvn -P test clean deploy
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
查看。
modules、dependencies、dependencyManagement標籤咱們在前文中已經講過, profile下這些標籤的可配置內容與其在根標籤(project)下的一致, profile下的配置能夠覆蓋根標籤下的配置。
repositories、pluginRepositories、reporting、distributionManagement 這些標籤在根標籤下也存在,只是咱們尚未聊到,與上面的這些標籤同樣, 這些標籤的可配置內容與其在根標籤下的一致,profile下的配置能夠覆蓋根標籤下的配置。
build標籤較爲特殊,其在profile下的可配置內容只包含前文中咱們提到的全部內容, 但在根標籤下的build標籤還有一些獨佔內容咱們沒有提到。
在project標籤下的build標籤下還有幾個標籤,這些標籤僅出如今project標籤下的build中, 沒法在profile中的內容配置。
這些標籤包括目錄結構標籤——sourceDirectory、sourceDirectory、testSourceDirectory、 outputDirectory、testOutputDirectory,以及extensions標籤。
這些標籤訂義了項目的目錄結構,標籤的含義和其英文名字一致,標籤的值必須爲絕對路徑 (通常使用`${project.basedir}進行拼接)。
這樣須要注意的是除了上述的幾個標籤外,咱們還能夠找到一個scriptSourceDirectory標籤, 該標籤實際沒有用處,已經被官宣廢棄。
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://root@x.x.x.x:/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>
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> <inherited>true</inherited> <configuration> <links> <link>http://java.sun.com/j2se/1.5.0/docs/a </link> </links> </configuration> </reportSet> </reportSets> </plugin> </plugins> </reporting>
與build下的plugin相比,reporting下的配置就相對較爲簡單,只有一個相似於executions 的reportSets標籤——二者均定義了插件在某一maven階段的構建任務。
有一點須要注意的是,一樣的插件能夠同時出如今build下和reporting下, 雖然咱們能夠用pluginManagement統一管理版本, 但build下的相關配置(configuration)不會傳遞到reporting中