點選「 IT碼徒 」, 關註,置頂 公眾號
每日技術幹貨,第一時間送達!
1
思維導圖
2
Maven 與構建
什麽是 Maven
轉譯:知識的積累、專家、內行。跨平台的計畫管理工具。Apache 組織的開源計畫。主要服務於基於 Java 平台的計畫構建、依賴管理和計畫資訊管理。
類似於 linux 平台的 yum、apt,前端領域的 npm。Maven 前身為 Ant 目前 tomcat 的源碼就是用 Ant 來構建和管理,更先進的工具有 Gradle, Spring 工程在用。
什麽是構建
何為構建:編譯、執行單元測試、生成文件、打包、部署的過程,這就是構建。
構建的步驟:
清理 clean:將以前編譯得到的舊檔 class 字節碼檔刪除。
編譯 compile:將 java 源程式編譯成 class 字節碼檔。
測試 test:自動測試,自動呼叫 junit 程式。
報告 report:測試程式執行的結果。
打包 package:動態 Web 工程打 War 包,java 工程打 jar 包。
安裝 install:將打包得到的檔復制到 「倉庫」 中的指定位置(Maven特定的概念)。
部署 deploy:將動態 Web 工程生成的 war 包復制到 Servlet 容器下,使其可以執行。
計畫骨架
pom:Project Object Model
根目錄:工程名
|---src:源碼
|---|---main:主程式
|---|---|---java:主程式程式碼路徑
|---|---|---resource:主程式配置檔路徑
|---|---test:測試
|---|---|---java:測試程式碼路徑
|---|---|---resource:測試配置檔路徑
|---pom.xml:maven 配置檔
簡單演示
## 1. 使用 archetype 命令生成 maven 簡單骨架
mvn archetype:generate -DarchetypeCatalog=internal
## 2. 編譯當前生成的計畫
mvn compile
## 3. 使用其他命令
mvn test-compile
mvn package
mvn clean
mvn install
mvn depoly 暫時不演示
3
座標與依賴
什麽是座標
類比為數學中平面幾何,座標(x、y ),任何一個座標都能唯一標識該平面中的一個點。
該點對應到 maven 就是 .jar、.war 等檔的檔。
Maven 使用 groupId 、 artifactId 、 version 、 packaging 、 classifier 等元素來組成自己的座標,並定義一組這樣的規則,只要能提供正確座標元素 Maven 就能找到對應的構件。
座標元素
groupId:定義當前 Maven 計畫隸屬的實際計畫。
artifactId:定義實際計畫中的一個 Maven 計畫(模組)。
packaging:定義 Maven 計畫打包方式。jar、war、pom。預設為 jar。
version:定義 Maven 計畫當前所處的版本。
classifier:區分從同一 artifact 構建的具有不同內容的構件。
classifier 使用場景
區分基於不同 JDK 版本的包
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.2.2</version>
< classifier>jdk13</ classifier>
<!--< classifier>jdk15</ classifier>-->
</dependency>
區分計畫的不同組成部份
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.2.2</version>
< classifier>jdk15-javadoc</ classifier>
<!--< classifier>jdk15-sources</ classifier> -->
</dependency>
構件名與座標是對應的,一般規則是: artifactId-version[- classifier].packaging 。
依賴聲明
<dependencies>
<dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<type></type>
<optional></optional>
<exclusions>
<exclusion>
<artifactId></artifactId>
<groupId></groupId>
</exclusion>
...
</exclusions>
</dependency>
...
</dependencies>
groupId、artifactId、version:依賴的基本座標。
type:依賴的型別,對應計畫對應的 packaging,一般不必聲明。
scope:依賴的範圍,後面詳解。
optional:標記依賴是否可選。
exclusions:用來排除傳遞性依賴。
依賴範圍
compile:編譯依賴範圍
如果沒有指定,預設使用該依賴範圍。對於編譯、測試、執行三種 classpath 都有效。如:spring-core。
test:測試依賴範圍
只對於測試 classpath 有效,只需要在編譯測試及執行測試才需要,在打包的時候不會打進去。如:JUnit。
provided:已提供依賴範圍
對於編譯和測試 classpath 有效,但執行時無效。如:servlet-api 編譯和測試計畫的時候都需要,但在實際執行中,容器已經提供,不需要 maven 重復的參照。
runtime:執行時依賴範圍
對於測試和執行的 classpath 有效,但在編譯主程式碼時無效。如:JDBC 驅動的實作包。只有在執行測試或者執行計畫時,才需要具體的 JDBC 驅動。
system:系統依賴範圍
與 provided 依賴範圍完全一致,但是使用該範圍時必須透過 systemPath 元素顯式地指定依賴檔的路徑。由於此類依賴不是透過 maven 倉庫解析的,而且往往與本機系統繫結,可能造成構建不可移植,因此應該謹慎使用。systemPath 元素可以參照環境變量,如:
<dependencies>
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdxt</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
</dependencies>
import:匯入依賴範圍
只在 dependencyManagement 標簽中生效,匯入已經定義好的 pom 檔中 dependencyManagement 節點內容
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.3.16.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
依賴機制與特性
依賴傳遞
A->B(compile):第一直接依賴
B->C(compile):第二直接依賴
A->C(compile):傳遞性依賴
當在A中配置
<dependency>
<groupId>com.B</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
</dependency>
則會自動匯入 C 包。
傳遞性依賴的範圍如下圖所示:
依賴調解
當傳遞性依賴出現問題時,能夠清楚地知道該傳遞性依賴是從哪條依賴路徑中引入的。
一、路徑最近者優先原則
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 依賴在 POM 檔中聲明順序在 C 依賴之前,那麽 Y(1.0) 則會被引入。如下依賴可用於測試:
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4.1</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
這裏有一點需要特別註意,看如下依賴:
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
按照兩原則,期望得到的結果應該是 1.11 版本的構建將被依賴。但實際結果卻依賴了 1.10 版本。what!這不是違反了 maven 依賴調解的最先定義原則?
其實這個是 dependency 外掛程式的功能,預設采用的是復寫的策略,當構建聲明處於同一 pom 中,且 groupid 和 artifactId 一致時,以最新聲明為準,後面的覆蓋前面的。
註意這裏沒涉及到依賴調解的功能。我的理解是依賴調解只發生於構建來自不同 pom 時,而此時構建聲明處於同一 pom,故不會觸發依賴調解。
可選依賴
A->B、B->X(可選)、B->Y(可選)。
計畫 A 依賴於計畫 B,計畫 B 依賴於計畫 X 和 Y。
理論上計畫 A 中,會把 B、X、Y 計畫都依賴進來。
但是 X、Y 兩個依賴對於 B 來講可能是互斥的,如 B 是資料庫隔離包,支持多種資料庫 MySQL、Oracle,在構建 B 計畫時,需要這兩種資料庫的支持,但在使用這個工具包時,只會依賴一個資料庫。
此時就需要在 B 計畫 pom 檔中將 X、Y 聲明為可選依賴,如下:
<dependency>
<groupId>com.X</groupId>
<artifactId>X</artifactId>
<version>1.0</version>
<optionnal>true</optionnal>
</dependency>
<dependency>
<groupId>com.Y</groupId>
<artifactId>Y</artifactId>
<version>1.0</version>
<optionnal>true</optionnal>
</dependency>
使用 optionnal 元素標識以後,只會對當前計畫 B 產生影響,當其他的計畫依賴 B 計畫時,這兩個依賴都不會被傳遞。
計畫 A 依賴於計畫 B,如果實際套用資料庫是 X, 則在 A 的 pom 中就需要顯式地聲明 X 依賴。
4
倉庫
倉庫分類:包括本地倉庫和遠端倉庫。其中遠端倉庫包括:私服和中央倉庫。搜尋構建的順序:
本地倉庫
maven settings profile 中的 repository;
pom.xml 中 profile 中定義的repository;
pom.xml 中 repositorys (按定義順序找);
maven settings mirror;
central 中央倉庫;
5
生命周期
Maven 的生命周期是為了對所有構建過程進行的抽象和統一,其中包含計畫的清理、初始化、編譯、測試、打包、整合測試、驗證、部署和站點生成等幾乎所有的構建步驟。
Maven 的生命周期是抽象的,本身是不做任何實際的工作。實際的任務都交給外掛程式來完成。
意味著 Maven 只在父類中定義了演算法的整體結構,子類別透過重寫父類的方法,來控制實際行為(設計模式中的樣版方法 Template Method)。虛擬碼如下:
publicabstract classAbstractBuilder{
publicvoidbuild(){
init();
compile();
test();
package();
integrationTest();
deploy();
}
protectedabstractvoidinit();
protectedabstractvoidcompile();
protectedabstractvoidtest();
protectedabstractvoidpackage();
protectedabstractvoidintegrationTest();
protectedabstractvoiddeploy();
}
三套生命周期
Maven 的生命周期並不是一個整體,Maven 擁有三套相互獨立的生命周期,它們分別為 clean、default 和 site。
clean 生命周期的目的是清理計畫;
default 生命周期的目的是構建計畫;
site 生命周期的目的是建立計畫站點;
單個生命周期執行順序
每個生命周期包含一些階段(phase),這些階段是有順序的,並且後面的階段依賴於前面的階段。
以 clean 生命周期為例,它包含的階段有 pre-clean、clean和post-clean。當呼叫 pre-clean 時,只有 pre-clean 階段得以執行;
當呼叫 clean 的時候,pre-clean和clean階段會得以順序執行,以此類推。
各個生命周期之間的關系
三套生命周期本身是相互獨立的,使用者可以僅呼叫 clean 生命周期的某個階段,或者僅僅呼叫 default 生命周期的某個階段,而不會對其他生命周期產生任何影響。
例如,當使用者呼叫 clean 生命周期的 clean 階段的時候,不會觸發 default 生命周期的任何階段,反之亦然。
生命周期各個階段詳解
clean
生命周期階段
描述
pre-clean | 執行一些清理前需要完成的工作。 |
clean | 清理上一次構建生成的檔。 |
post-clean | 執行一些清理後需要完成的工作。 |
default
包含 23 個階段,此處只介紹重點步驟,如下表:
生命周期階段
描述
validate | 檢查工程配置是否正確,完成構建過程的所有必要資訊是否能夠獲取到。 |
initialize | 初始化構建狀態,例如設定內容。 |
generate-sources | |
process-sources | 處理計畫資原始檔,處理計畫主資原始檔。一般來說,是對src/main/resources目錄的內容進行變量替換等工作後,復制到計畫輸出的主 classpath目錄中。 |
generate-resources | |
process-resources | |
compile | 編譯計畫的主源碼。一般來說,是編譯src/main/java目錄下的Java檔至計畫輸出的主 classpath目錄中。 |
process- classes | 處理編譯生成的檔,例如 Java class 字節碼的加強和最佳化。 |
generate-test-sources | |
process-test-sources | 處理計畫測試資原始檔。一般來說,是對src/test/resources目錄的內容進行變量替換等工作後,復制到計畫輸出的測試 classpath目錄中。 |
test-compile | 編譯計畫的測試程式碼。一般來說,是編譯src/test/java目錄下的Java檔至計畫輸出的測試 classpath目錄中。 |
process-test- classes | |
test | 使用適當的單元測試框架(例如JUnit)執行測試。 |
prepare-package | 在真正打包之前,為準備打包執行任何必要的操作。 |
package | 獲取編譯後的程式碼,並按照可釋出的格式進行打包,例如 JAR、WAR 或者 EAR 檔。 |
pre-integration-test | 在整合測試執行之前,執行所需的操作。例如,設定所需的環境變量。 |
integration-test | 處理和部署必須的工程包到整合測試能夠執行的環境中。 |
post-integration-test | 在整合測試被執行後執行必要的操作。例如,清理環境。 |
verify | 執行檢查操作來驗證工程包是有效的,並滿足品質要求。 |
install | 安裝工程包到本地倉庫中,該倉庫可以作為本地其他工程的依賴。 |
deploy | 拷貝最終的工程包到遠端倉庫中,以共享給其他開發人員和工程。 |
site
生命周期階段
描述
pre-site | 執行一些在生成計畫站點之前需要完成的工作。 |
site | 生成計畫站點文件。 |
post-site | 執行一些在生成計畫站點之後需要完成的工作。 |
site-deploy | 將生成的計畫站點釋出到伺服器上。 |
5
外掛程式
Maven 三套生命周期定義各個階段不做任何實際工作,實際工作都是由外掛程式來完成的,每個生命周期階段都是由外掛程式的目標來完成。在 pom 檔中聲明如下(打包源碼檔外掛程式):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
外掛程式目標
一個外掛程式有可能有多個功能、每個功能就是一個目標。比如 maven-dependency-plugin 有十多個目標,每個目標對應了一個功能。
外掛程式的目標為 dependency:analyze、dependency:tree和dependency:list。
通用寫法:冒號前面是外掛程式字首,冒號後面是外掛程式的目標。比如 compiler:compile。
外掛程式繫結
內建繫結
為實作快速構建,Maven 有一套內建的外掛程式繫結。三套生命周期的外掛程式繫結具體如下(其實是各個生命周期階段與外掛程式的目標的繫結)。
其中 default 生命周期的構建方式會其打包型別有關、打包型別在POM中 packaging 指定。一般有 jar、war 兩種型別。下面是預設繫結外掛程式與生命周期關系圖:
自訂繫結
自訂繫結允許我們自己掌控外掛程式目標與生命周期的結合。以生成計畫主程式碼的源碼 jar 為例。
使用到的外掛程式和它的目標為:maven-source-plugin:jar-no-fork。將其繫結到 default 生命周期階段 verify 上(可以任意指定三套生命周期的任意階段)。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<!-- 指定作用在生命周期的哪個階段 -->
<phase>verify</phase>
<goals>
<!-- 指定執行繫結外掛程式的哪些目標 -->
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
外掛程式配置
使用命令列配置
在 maven 命令中加入 -D 參數,並伴隨一個參數鍵=參數值的形式,來配置外掛程式目標參數。
如:maven-surefire-plugin 外掛程式提供一個 maven.test.skip 參數,當值為 true 時會跳過執行測試:
-- 對比 mvn install
mvn install –Dmaven.test.skip=true
使用 pom 全域配置
在聲明外掛程式的時候,對外掛程式進行一個全域配置,後面所有使用該外掛程式的都要遵循這個配置。比如指定 maven-compile-plugin 編譯 1.7 版本的原始檔:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
6
聚合與繼承
聚合: 為了一次構建多個計畫模組,就需要對多個計畫模組進行聚合
<modules>
<module>模組一</module>
<module>模組二</module>
<module>模組三</module>
</modules>
繼承: 為了消除重復,把很多相同的配置提取出來,例如:dependency、grouptId,version 等
<parent>
<groupId>com.xxxx.maven</groupId>
<artifactId>parent-project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../ParentProject/pom.xml</relativePath>
</parent>
以下的元素是可以被繼承的:
groupId,計畫組ID;
version,計畫版本;
description,計畫描述資訊;
organazation,計畫的組織資訊;
inceptionYear,計畫的創始年份;
developers,計畫開發者資訊;
contributors,計畫的貢獻者資訊;
distributionManagement,計畫的部署資訊;
issueManagement,計畫的缺陷跟蹤系統資訊;
ciManagement,計畫的持續整合系統資訊;
scm,計畫的版本控制系統資訊;
mailingLists,計畫的信件列表資訊;
properties,自訂的Maven內容;
dependencies,計畫的依賴配置;
dependencyManagement,計畫的依賴管理配置;
repositories,計畫的倉庫配置;
build,包括計畫的源碼目錄配置、輸出目錄配置、外掛程式配置、外掛程式管理配置等;
reporting,包括計畫的報告輸出目錄配置、報告外掛程式配置。
註意下面的元素,這些都是不能被繼承的:
artifactId
name
prerequisites
聚合與繼承之間的關系
兩者共同點為,打方式必須都是 pom
在實際的計畫中,一個 pom 既是聚合 pom 又是父 pom
註:父 pom 中使用 dependencies 引入的依賴也會被子 pom 繼承,所以不要將過多的實際依賴放在父 pom,父 pom 只用於管理,使用 dependencyManagement 標簽。
7
靈活構建
使用內容、 resources 外掛程式資源過濾功能(filter)和 Maven 的 profile 功能,實作環境的靈活切換
內容
透過 properties 元素使用者可以自訂一個或者多個 Maven 內容,然後在 pom 其他的地方使用 ${內容名} 的方式參照該內容,這種方式最大意義在於消除重復。
一、內建內容
${basedir} 表示計畫根目錄,即包含 pom.xml 檔的目錄
${version} 等同於或者 {pom.version} 表示計畫版本
二、POM 內容
所有 pom 中的元素都可以用 project. 例如 ${project.artifactId} 對應了 < project>元素的值。常用的 POM 內容包括:
${project.build.sourceDirectory} : 計畫的主源碼目錄,預設為 src/main/java/.
${project.build.testSourceDirectory} : 計畫的測試源碼目錄,預設為 /src/test/java/.
${project.build.directory} : 計畫構建輸出目錄,預設為 target/.
${project.build.outputDirectory} : 計畫主程式碼編譯輸出目錄,預設為 target/ classes/.
${project.build.testOutputDirectory} : 計畫測試程式碼編譯輸出目錄,預設為 target/test classes/.
${project.groupId}: 計畫的 groupId.
${project.artifactId} : 計畫的 artifactId.
${project.version} : 計畫的 version, 等同於 ${version}
${project.build.finalName} : 計畫打包輸出檔的名稱,預設為 ${project.artifactId}${project.version}
三、自訂內容
在 pom 中元素下自訂的 Maven 內容
<properties>
<swagger.version>2.2.2</swagger.version>
</properties>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
四、Settings 內容
所有用的的 settings.xml 中的設定都可以透過 settings。字首進行參照與 POM 內容同理。
如 ${settings.localRepository} 指向使用者本地倉庫的地址
五、Java 系統內容
所有 Java 系統內容都可以使用 Maven 內容參照,例如 ${user.home} 指向了使用者目錄。
可以透過命令列 mvn help:system 檢視所有的 Java 系統內容
六、環境變量內容
所有環境變量都可以使用以 env. 開頭的 Maven 內容參照。例如 ${env.JAVA_HOME} 指代了 JAVA_HOME 環境變量的值。
也可以透過命令列 mvn help:system 檢視所有環境變量。
七、父級工程內容
上級工程的 pom 中的變量用字首 引 用 。 上 級 工 程 的 版 本 也 可 以 這 樣 引 用 {parent.version}
Profile
profile 特性可以讓我們定義多個 profile,然後每個 profile 對應不同的啟用條件和配置資訊,從而達到不同環境使用不同配置資訊的效果。
profile 可以在以下幾個地方聲明:
m.xml:這裏聲明的 profile 只對當前計畫有效
使用者 settings.xml:.m2/settings.xml 中的 profile 對該使用者的 Maven 計畫有效
全域 settings.xml:conf/settings.xml,對本機上所有 Maven 計畫有效
範例:
<project>
...
<profiles>
<profile>
<id>dev</id>
<properties>
<active.profile>dev</active.profile>
<key1>value1</key1>
<key2>value2</key2>
</properties>
<!-- 預設啟用配置 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<!-- 在該 profile 下才會引入的依賴 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependencies>
<!-- 在該 profile 下才會載入的變量檔 -->
<build>
<filters>
<filter>../profile/test-pre.properties</filter>
</filters>
</build>
</profile>
</profiles>
...
</project>
— END —
PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。
往期推薦