引言
Maven
應該是大家的老熟客了,身為
Java
程式設計師,幾乎每天都會跟他打交道。
不過有趣的是:
很多夥伴對
Maven
,似乎很熟,但又好像不熟;在理解上,處於似懂非懂的「量子纏結態」
,為什麽這麽說呢?原因很簡單,要說不熟吧,偏偏每天都有所接觸;要說熟吧,可是對許多高級功能又僅是一知半解。
正因如此,為了輔助大家從「量子纏結態」中走出來,本文會從零開始,帶著大家玩轉
Maven
技術。當然,其實寫這篇文章更大的目的,是為後續寫
【漫談分布式】
專欄做準備,畢竟後續會頻繁用到
Maven
構建多工程計畫。
一、Maven快速上手/回顧
1.1、Maven工作原理剖析
在
Maven
中,節點會分為工程、倉庫兩大類,工程是「依賴使用者」,倉庫是「依賴提供者」,關系如下:
看著或許有點頭大,要講明白得先弄清裏面三種倉庫:
• 中央倉庫:就是前面配置的映像源,裏面擁有海量的公共
jar
包資源;
• 遠端倉庫:也叫私服倉庫,主要儲存公司內部的
jar
包資源,這個後續會細說;
• 本地倉庫:自己電腦原生的倉庫,會在磁盤上儲存
jar
包資源。
大致了解三種倉庫的含義後,接著來梳理
Maven
的工作流程:
• ①計畫透過
GAV
座標引入依賴,首先會去本地倉庫尋找
jar
包;
• ②如果在本地倉庫中找到了,直接把依賴載入到當前工程的
External Libraries
中;
• ③如果沒找到,則去讀取
settings.xml
檔,判斷是否存在私服配置;
• ④如果有私服配置,根據配置的地址找到遠端倉庫,接著拉取依賴到本地倉庫;
• ⑤如果遠端倉庫中沒有依賴,根據私服配置去中央倉庫拉取,然後放到私服、本地倉庫;
• ⑥從遠端或中央倉庫中,把依賴下載到本地後,再重復第二步,把依賴載入到計畫中。
上述六步便是
Maven
的完整工作流程,可能許多人沒接觸過私服,這個會放到後面聊。如果你的計畫沒配置
Maven
私服,那麽第三步時,會直接從
settings.xml
讀取映像源配置,直接去到中央倉庫拉取依賴。
不過這裏有個問題,拉取/引入依賴時,
Maven
是怎麽知道要找誰呢?答案是依靠
GAV
座標,大家可以去觀察一下本地倉庫,當你引入一個依賴後,本地倉庫中的目錄,會跟你的
GAV
座標一一對應,如:
無論是什麽型別的倉庫,都會遵循這個原則進行構建,所以,只要你書寫了正確的
GAV
座標,就一定能夠找到所需的依賴,並將其載入到計畫中。
1.2、Maven生命周期
透過
IDEA
工具的輔助,能很輕易看見
Maven
的九種
Lifecycle
命令,如下:
雙擊其中任何一個,都會執行相應的
Maven
構建動作,為啥
IDEA
能實作這個功能呢?道理很簡單,因為
IDEA
封裝了
Maven
提供的命令,如:點選圖中的
clean
,本質是在當前目錄中,執行了
mvn clean
命令,下面解釋一下每個命令的作用:
•
clean
:清除當前工程編譯後生成的檔(即刪除
target
整個目錄);
•
validate
:對工程進行基礎驗證,如工程結構、
pom
、資原始檔等是否正確;
•
compile
:對
src/main/java
目錄下的源碼進行編譯(會生成
target
目錄);
•
test
:編譯並執行
src/test/java/
目錄下的所有測試用例;
•
package
:將當前計畫打包,普通計畫打
jar
包,
webapp
計畫打
war
包;
•
verify
:驗證工程所有程式碼、配置進行是否正確,如類中程式碼的語法檢測等;
•
install
:將當前工程打包,然後安裝到本地倉庫,別人可透過
GAV
匯入;
•
site
:生成計畫的概述、源碼測試覆蓋率、開發者列表等站點文件(需要額外配置);
•
deploy
:將當前工程對應的包,上傳到遠端倉庫,提供給他人使用(私服會用)。
上述便是九個周期階段命令的釋義,而
Maven
總共劃分了三套生命周期:
主要看
default
這套,該生命周期涵蓋了構建過程中的檢測、編譯、測試、打包、驗證、安裝、部署每個階段。註意一點:
同一生命周期內,執行後面的命令,前面的所有命令會自動執行
!比如現在執行一條命令:
mvn test
test
命令位於
default
這個生命周期內,所以它會先執行
validate、compile
這兩個階段,然後才會真正執行
test
階段。同時,還可以一起執行多個命令,如:
mvn clean install
這兩個命令隸屬於不同的周期,所以會這樣執行:先執行
clean
周期裏的
pre-clean、clean
,再執行
default
周期中,
validate~install
這個閉區間內的所有階段。
從上面不難發現,
default
是
Maven
的核心周期,但其實上面並沒有給完整,因為官方定義的
default
一共包含
23
個小階段,上面的圖只列出了七個核心周期,對詳細階段感興趣的可以自行了解。
Maven
中只定義了三套生命周期,以及每套周期會包含哪些階段,而每個階段具體執行的操作,這會交給外掛程式去幹,也就是說:**
Maven
外掛程式會實作生命周期中的每個階段**,這也是大家為什麽看到
IDEA
的
Lifecycle
下面,還會有個
Plugins
的原因:
當你雙擊
Lifecycle
中的某個生命周期階段,實際會呼叫
Plugins
中對應的外掛程式。在
Shell
視窗執行
mvn
命令時,亦是如此,因為外掛程式對應的實作包,都會以
jar
包形式儲存在本地倉柯瑞。
你有特殊的需求,也可以在
pom.xml
的
<build>
標簽中,依靠
<plugins>
外掛程式來匯入。
二、Maven進階操作
上面所說到的一些知識,僅僅只是
Maven
的基本操作,而它作為
Java
計畫管理占有率最高的工具,還提供了一系列高階功能,例如內容管理、多模組開發、聚合工程等,不過這裏先來說說依賴沖突。
2.1、依賴沖突
依賴沖突是指:
在
Maven
計畫中,當多個依賴包,引入了同一份類別庫的不同版本時,可能會導致編譯錯誤或執行時異常
。這種情況下,想要解決依賴沖突,可以靠升級/降級某些依賴項的版本,從而讓不同依賴引入的同一類別庫,保持一致的版本號。
另外,還可以透過隱藏依賴、或者排除特定的依賴項來解決問題。但是想搞明白這些,首先得理解
Maven
中的依賴傳遞性,一起來看看。
2.1.1、依賴的傳遞性
先來看個例子:
目前的工程中,僅匯入了一個
spring-web
依賴,可是從下面的依賴樹來看,
web
還間接依賴於
beans、core
包,而
core
包又依賴於
jcl
包,此時就出現了依賴傳遞,所謂的依賴傳遞是指:
當引入的一個包,如果依賴於其他包(類別庫),當前的工程就必須再把其他包引入進來
。
這相當於無限套娃,而這類「套娃」引入的包,被稱為間接性依賴。與之對應的是直接性依賴,即:
當前工程的
pom.xml
中,直接透過
GAV
座標引入的包
。既然如此,那麽一個工程內的依賴包,就必然會出現層級,如:
在這裏我們僅引入了一個
boot-test
座標,但當開啟依賴樹時,會發現這一個包,依賴於其他許多包,而它所依賴的包又依賴於其他包……,如此不斷套娃,最深套到了五層。而不同的包,根據自己所處的層級不同,會被劃分為
1、2、3、4……
級。
2.1.2、自動解決沖突問題
Maven
作為
Apache
旗下的產品,而且還經過這麽多個版本叠代,對於依賴沖突問題,難道官方想不到嗎?必然想到了,所以在絕對大多數情況下,依賴沖突問題並不需要我們考慮,
Maven
工具會自動解決,怎麽解決的呢?就是基於前面所說的依賴層級,下面來詳細說說。
①層級優先原則
,
Maven
會根據依賴樹的層級,來自動剔除相同的包,層級越淺,優先級越高。這是啥意思呢?同樣來看個例子:
我們又透過
GAV
座標匯入了
spring-web
包,根據前面所說,
web
依賴於
beans、core
包,而
beans
包又依賴於
core
包,此時註意,這裏出現了兩個
core
包,前者的層級為
2
,後者的層級為
3
,所以
Maven
會自動將後者剔除,這點從圖中也可明顯看出,層級為
3
的
core
直接變灰了。
②聲明優先原則 ,上條原則是基於層級深度,來自動剔除沖突的依賴,那假設同級出現兩個相同的依賴怎麽辦?來看例子:
此時用
GAV
引入了
web、jdbc
兩個包,來看右邊的依賴樹,
web
依賴於
beans、core
包,
jdbc
也依賴於這兩個包,此時相同層級出現了依賴沖突,可從結果上來看,後面
jdbc
所依賴的兩個包被剔除了,能明顯看到一句:
omitted for duplicate
,這又是為啥呢?因為根據聲明優先原則,
同層級出現包沖突時,先聲明的會覆蓋後聲明的,為此後者會被剔除
。
③配置優先原則 ,此時問題又又來了,既然相同層級出現同版本的類別庫,前面的會覆蓋後面的,可是當相同層級,出現不同版本的包呢?依舊來看例子:
此時
pom
引入了兩個
web
包,前者版本為
5.1.8
,後者為
5.1.2
,這兩個包的層級都是
1
,可是看右邊的依賴樹,此時會發現,
5.1.8
壓根沒引進來啊!為啥?這就是配置優先原則,
同級出現不同版本的相同類別庫時,後配置的會覆蓋先配置的
。
所以大家發現了嘛?在很多時候,並不需要我們考慮依賴沖突問題,
Maven
會依據上述三條原則,幫我們智慧化自動剔除沖突的依賴,其他包都會共享留下來的類別庫,只有當出現無法解決的沖突時,這才需要咱們手動介入。
通常來說,
Maven
如果無法自動解決沖突問題,會在構建過程中丟擲異常並提供相關資訊,這時大家可以根據給出的資訊,手動排除指定依賴。
2.1.3、主動排除依賴
所謂的排除依賴,即是指從一個依賴包中,排除掉它依賴的其他包,如果出現了
Maven
無法自動解決的沖突,就可以基於這種手段進行處理,例如:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.8.RELEASE</version>
<exclusions>
<!-- 排除web包依賴的beans包 -->
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
</exclusions>
</dependency>
從圖中結果可以明顯看出,透過這種方式,可以手動移除包所依賴的其他包。當出現沖突時,透過這種方式將沖突的兩個包,移除掉其中一個即可。
其實還有種叫做「隱藏依賴」的手段,不過這種手段是用於多工程聚合計畫,所以先講清楚「多模組/工程」計畫,接著再講「隱藏依賴」。
2.2、Maven分模組開發
現如今,一個稍具規模的完整計畫,通常都要考慮接入多端,如
PC、WEB、APP
端等,那此時問題來了,每個端之間的邏輯,多少會存在細微差異,如果將所有程式碼融入在一個
Maven
工程裏,這無疑會顯得十分臃腫!為了解決這個問題,
Maven
推出了分模組開發技術。
所謂的分模組開發,
即是指建立多個
Maven
工程,組成一個完整計畫
。通常會先按某個維度劃分出多個模組,接著為每個模組建立一個
Maven
工程,典型的拆分維度有三個:
• ①接入維度:按不同的接入端,將計畫劃分為多個模組,如
APP、WEB
、小程式等;
• ②業務維度:根據業務性質,將計畫劃分為一個個業務模組,如前台、後台、使用者等;
• ③功能維度:共用程式碼做成基礎模組,業務做成一個模組、
API
做成一個模組……。
當然,通常①、②會和③混合起來用,比如典型的「先根據程式碼功能拆分,再根據業務維度拆分」。
相較於把所有程式碼揉在一起的「大鍋飯」,多模組開發的好處特別明顯:
• ①簡化計畫管理,拆成多個模組/子系統後,每個模組可以獨立編譯、打包、釋出等;
• ②提高程式碼復用性,不同模組間可以相互參照,可以建立公共模組,減少程式碼冗余度;
• ③方便團隊協作,多人各司其職,負責不同的模組,
Git
管理時也能減少交叉沖突;
• ④構建管理度更高,更方便做持續整合,可以根據需要靈活配置整個計畫的構建流程;
• ……
不過
Maven2.0.9
才開始支持聚合工程,在最初的時期裏,想要實作分模組開發,需要手動先建立一個空的
Java
計畫(
Empty Project
):
接著再在其中建立多個
Maven Project
:
然後再透過
mvn install
命令,將不同的
Maven
計畫安裝到本地倉庫,其他工程才能透過
GAV
座標引入。
這種傳統方式特別吃力,尤其是多人開發時,另一個模組的程式碼更新了,必須手動去更新本地倉庫的
jar
包;而且多個模組之間相互依賴時,構建起來額外的麻煩!正因如此,官方在後面推出了「聚合工程」,下面聊聊這個。
2.3、Maven聚合工程
所謂的聚合工程,即是指: 一個計畫允許建立多個子模組,多個子模組組成一個整體,可以統一進行計畫的構建 。不過想要弄明白聚合工程,得先清楚「父子工程」的概念:
• 父工程:不具備任何程式碼、僅有
pom.xml
的空計畫,用來定義公共依賴、外掛程式和配置;
• 子工程:編寫具體程式碼的子計畫,可以繼承父工程的配置、依賴項,還可以獨立拓展。
而
Maven
聚合工程,就是基於父子工程結構,來將一個完整計畫,劃分出不同的層次,這種方式可以很好的管理多模組之間的依賴關系,以及構建順序,大大提高了開發效率、維護性。並且當一個子工程更新時,聚合工程可以保障同步更新其他存在關聯的子工程!
2.3.1、聚合工程入門指南
理解聚合工程是個什麽東東之後,接著來聊聊如何建立聚合工程,首先要建立一個空的
Maven
計畫,作為父工程,這時可以在
IDEA
建立
Maven
計畫時,把打包方式選成
POM
,也可以建立一個普通的
Maven
計畫,然後把
src
目錄刪掉,再修改一下
pom.xml
:
<!-- 寫在當前計畫GAV座標下面 -->
<packaging>pom</packaging>
這樣就得到了一個父工程,接著可以在此基礎上,繼續建立子工程:
當點選
Next
後,大家會發現:
這時無法手動指定
G、V
了,而是會從父工程中繼承,最終效果如下:
這裏我建立了兩個子工程,所以父工程的
pom.xml
中,會用一個
<modules>
標簽,來記錄自己名下的子工程列表,而子工程的
pom
頭,也多了一個
<parent>
標簽包裹!大家看這個標簽有沒有眼熟感?大家可以去看一下
SpringBoot
計畫,每個
pom.xml
檔的頭,都是這樣的。
這裏提個問題:
子工程下面能不能繼續建立子工程
?答案
Yes
,你可以無限套娃下去,不過我的建議是:一個聚合計畫,最多只能有三層,路徑太深反而會出現稀奇古怪的問題。
2.3.2、聚合工程的依賴管理
前面搭建好了聚合工程,接著來看個問題:
zhuzi_001、002
兩個子工程中,各自引入了三個依賴,可觀察上圖會發現,兩者引入的依賴僅有一個不同,其余全部一模一樣!所以這時,就出現了「依賴冗余」問題,那有沒有好的方式解決呢?答案是有的,前面說過:
公共的依賴、配置、外掛程式等,都可以配置在父工程裏
,如下:
當把公共的依賴定義在父工程中,此時觀察圖中右側的依賴樹,會發現兩個子工程都繼承了父依賴。
不過此時問題又來了!為了防止不同子工程引入不同版本的依賴,最好的做法是在父工程中,統一對依賴的版本進行控制,規定所有子工程都使用同一版本的依賴,怎麽做到這點呢?可以使用
<dependencyManagement>
標簽來管理,例如:
在父工程中,
<dependencies>
裏只定義了一個
webmvc
依賴,而
<dependencyManagement>
中定義了
druid、test、jdbc
三個依賴,這兩個標簽有何區別呢?
•
<dependencies>
:定義強制性依賴,寫在該標簽裏的依賴項,子工程必須強制繼承;
•
<dependencyManagement>
:定義可選性依賴,該標簽裏的依賴項,子工程可選擇使用。
相信這樣解釋後,大家對於兩個標簽的區別,就能一清二楚了!同時註意,子工程在使用
<dependencyManagement>
中已有的依賴項時,不需要寫
<version>
版本號,版本號在父工程中統一管理,這就滿足了前面的需求。這樣做的好處在於:
以後為計畫的技術棧升級版本時,不需要單獨修改每個子工程的
POM
,只需要修改父
POM
檔即可,大大提高了維護性
!
2.3.3、聚合工程解決依賴沖突
之前傳統的
Maven
計畫會存在依賴沖突問題,那聚合工程中存不存在呢?當然存在,比如
001
中引入了
jdbc、test
這兩個包,而
002
中也引入了,這時假設把
001
工程打包到本地倉庫,在
002
工程中引入時,此時依賴是不是又沖突了?
Yes
,怎麽處理呢?先看例子:
在上圖中,
001
引入了
aop
包,接著透過
install
操作,把
001
工程打到了本地倉庫。於是,在
002
工程中,引入了
web、zhuzi_001
這兩個包。根據前面所說的依賴傳遞原則,
002
在引入
001
時,由於
001
參照了別的包,所以
002
被迫也引入了其他包。
還是那句話,大多數情況下,
Maven
會基於那三條原則,自動幫你剔除重復的依賴,如上圖右邊的依賴樹所示,
Maven
自動剔除了重復依賴。這種結果顯然是好現象,可是萬一
Maven
不能自動剔除怎麽辦?這時就需要用到最開始所說的「隱藏依賴」技術了!
修改
001
的
pom.xml
,如下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.8.RELEASE</version>
<optional>true</optional>
</dependency>
眼尖的小夥應該能發現,此時多了一個
<optional>
標簽,該標簽即是「隱藏依賴」的開關:
•
true
:開啟隱藏,當前依賴不會向其他工程傳遞,只保留給自己用;
•
false
:預設值,表示當前依賴會保持傳遞性,其他引入當前工程的計畫會間接依賴。
此時重新把
001
打到本地倉庫,再來看看依賴樹關系:
當開啟隱藏後,其他工程引入當前工程時,就不會再間接引入當前工程的隱藏依賴,因此來手動排除聚合工程中的依賴沖突問題。其他許多資料裏,講這塊時,多少講的有點令人迷糊,而相信看到這裏,大家就一定理解了
Maven
依賴管理。
2.3.4、聚合工程的構建
前面說到過,
Maven
聚合工程可以對所有子工程進行統一構建,這是啥意思呢?如果是傳統的分模組計畫,需要挨個進行打包、測試、安裝……等工作,而聚合工程則不同,來看
IDEA
提供的
Maven
輔助工具:
尾巴上帶有
root
標識的工程,意味著這是一個父工程,在我們的案例中,有一個父、兩個子,來看
IDEA
的工具,除開給兩個子工程提供了
Lifecycle
命令外,還給父工程提供了一套
Lifecycle
命令,這兩者的區別在哪兒呢?當你雙擊父工程的某個
Lifecycle
命令,它找到父
POM
的
<modules>
標簽,再根據其中的子工程列表,完成對整個聚合工程的構建工作。
大家可以去試一下,當你雙擊父工程
Lifecycle
下的
clean
,它會把你所有子工程的
target
目錄刪除。同理,執行其他命令時也一樣,比如
install
命令,雙擊後它會把你所有的子工程,打包並安裝到本地倉庫,不過問題又又又來了!
假設這裏
001
參照了
002
,
002
又參照了
001
,兩者相互參照,
Maven
會如何構建啊?到底該先打包
001
,還是該先打包
002
?我沒去看過
Lifecycle
外掛程式的源碼,不過相信背後的邏輯,應該跟
Spring
解決依賴迴圈類似,感興趣的小夥伴可以自行去研究。不過我這裏聲明一點:**
Maven
聚合工程的構建流程,跟
<modules>
標簽裏的書寫順序無關,它會自行去推斷依賴關系,從而完成整個計畫的構建**。
2.3.5、聚合打包跳過測試
當大家要做計畫發版時,就需要對整個聚合工程的每個工程打包(
jar
或
war
包),此時可以直接雙擊父工程裏的
package
命令,但
test
命令在
package
之前,按照之前聊的生命周期原則,就會先執行
test
,再進行打包。
test
階段,會去找到所有子工程的
src/test/java
目錄,並執行裏面的測試用例,如果其中任何一個報錯,就無法完成打包工作。而且就算不報錯,執行所有測試用例也會特別耗時,這時該怎麽辦呢?可以選擇跳過
test
階段,在
IDEA
工具裏的操作如下:
先選中
test
命令,接著點選上面的閃電圖示,這時
test
就會畫上橫線,表示該階段會跳過。如果你是在用
mvn
命令,那麽打包跳過測試的命令如下:
mvn package –D skipTests
同時大家還可以在
pom.xml
裏,配置外掛程式來精準控制,比如跳過某個測試類不執行,配置規則如下:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests>
<includes>
<!-- 指定要執行的測試用例 -->
<include>**/XXX*Test.java</include>
</includes>
<excludes>
<!-- 執行要跳過的測試用例 -->
<exclude>**/XXX*Test.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
不過這個功能有點雞肋,了解即可,通常不需要用到。
2.4、Maven內容
回到之前案例的父工程
POM
中,此時來思考一個問題:
雖然我們透過
<dependencyManagement>
標簽,來控制了子工程中的依賴版本,可目前還有一個小問題:
版本冗余
!比如現在我想把
Spring
版本從
5.1.8
升級到
5.2.0
,雖然不需要去修改子工程的
POM
檔,可從上圖中大家會發現,想升級
Spring
的版本,還需要修改多處地方!
咋辦?總不能只升級其中一個依賴的版本吧?可如果全部都改一遍,無疑就太累了……,所以,這裏我們可以透過
Maven
內容來做管理,我們可以在
POM
的
<properties>
標簽中,自訂內容,如:
<properties>
<spring.version>5.2.0.RELEASE</spring.version>
</properties>
而在
POM
的其他位置中,可以透過
${}
來參照該內容,例如:
這樣做的好處特別明顯,現在我想升級
Spring
版本,只需要修改一處地方即可!
除開可以自訂內容外,
Maven
也會有很多內建內容,大體可分為四類:
型別 | 使用方式 |
Maven
內建內容
|
${內容名},如
${version}\
|
計畫環境內容 |
${
setting.
內容名},如
${settings.localRepository}\
|
Java
環境變量
|
${
.
內容名},如
${java. class.path}\
|
系統環境變量 |
${
env.
內容名},如
${env.USERNAME}\
|
不過這些用的也不多,同時不需要記,要用的時候,
IDEA
工具會有提示:
2.5、Maven多環境配置
實際工作會分為開發、測試、生產等環境,不同環境的配置資訊也略有不同,而大家都知道,我們可以透過
spring.profiles.active
內容,來動態使用不同環境的配置,而
Maven
為何又整出一個多環境配置出來呢?想要搞清楚,得先搭建一個
SpringBoot
版的
Maven
聚合工程。
首先建立一個只有
POM
的父工程,但要註意,這裏是
SpringBoot
版聚合計畫,需稍微改造:
<!-- 先把Spring Boot Starter聲明為父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<!-- 當前父工程的GAV座標 -->
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhuzi</groupId>
<artifactId>maven_zhuzi</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- 配置JDK版本 -->
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<!-- 引入SpringBootWeb的Starter依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 引入SpringBoot整合Maven的外掛程式 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
對比普通聚合工程的父
POM
來說,
SpringBoot
版的聚合工程,需要先把
spring-boot-starter
聲明成自己的「爹」,同時需要引入
SpringBoot
相關的外掛程式,並且我在這裏還引入了一個
boot-web
依賴。
接著來建立子工程,在建立時記得選
SpringBoot
樣版來建立,不過建立後記得改造
POM
:
<!-- 聲明父工程 -->
<parent>
<artifactId>maven_zhuzi</artifactId>
<groupId>com.zhuzi</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 子工程的描述資訊 -->
<artifactId>boot_zhuzi_001</artifactId>
<name>boot_zhuzi_001</name>
<description>Demo project for Spring Boot</description>
就只需要這麽多,因為
SpringBoot
的外掛程式、依賴包,在父工程中已經聲明了,這裏會繼承過來。
接著來做
Maven
多環境配置,找到父工程的
POM
進行修改,如下:
<profiles>
<!-- 開發環境 -->
<profile>
<id>dev</id>
<properties>
<profile.active>dev</profile.active>
</properties>
</profile>
<!-- 生產環境 -->
<profile>
<id>prod</id>
<properties>
<profile.active>prod</profile.active>
</properties>
<!-- activeByDefault=true,表示打包時,預設使用這個環境 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 測試環境 -->
<profile>
<id>test</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>
配置完這個後,重新整理當前
Maven
工程,
IDEA
中就會出現這個:
預設停留在
prod
上,這是因為
POM
中用
<activeByDefault>
標簽指定了,接著去到子工程的
application.yml
中,完成
Spring
的多環境配置,如下:
# 設定啟用的環境
spring:
profiles:
active:${profile.active}
---
# 開發環境
spring:
profiles:dev
server:
port:80
---
# 生產環境
spring:
profiles:prod
server:
port:81
---
# 測試環境
spring:
profiles:test
server:
port:82
---
這裏可以透過檔來區分不同環境的配置資訊,但我這裏為了簡單,就直接用
---
進行區分,這組配置大家應該很熟悉,也就是不同的環境中,使用不同的埠號,但唯一不同的是:**以前
spring.profiles.active
內容會寫上固定的值,而現在寫的是
${profile.active}
**,這是為什麽呢?
這代表從
pom.xml
中,讀取
profile.active
內容值的意思,而父
POM
中配了三組值:
dev、prod、test
,所以當前子工程的
POM
,也會繼承這組配置,而目前預設勾選在
prod
上,所以最終
spring.profiles.active=prod
,不過想要在
application.yml
讀到
pom.xml
的值,還需在父
POM
中,加一個依賴和外掛程式:
<!-- 開啟 yml 檔的 ${} 取值支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.5.RELEASE</version>
<optional>true</optional>
</dependency>
<!-- 添加外掛程式,將計畫的資原始檔復制到輸出目錄中 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<encoding>UTF-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
最後來嘗試啟動子工程,操作流程如下:
• ①在
Maven
工具的
Profiles
中勾選
dev
,並重新整理當前計畫;
• ②接著找到子工程的啟動類,並右鍵選擇
Run ……
啟動子計畫。
先仔細看執行的結果,我來解釋一下執行流程:
• ①啟動時,
pom.xml
根據勾選的
Profiles
,使用相應的
dev
環境配置;
• ②
yml
中
${profile.active}
會讀到
profile.active=dev
,使用
dev
配置組;
• ③
application.yml
中的
dev
配置組,
server.port=80
,所以最終透過
80
埠啟動。
看完這個流程,大家明白最開始那個問題了嗎?
Maven
為何還整了一個多環境配置?
大家可能有種似懂非懂的感覺,這裏來說明一下,先把環境換到微服務計畫中,假設有
20
個微服務,此時計畫要上線或測試,所以需要更改配置資訊,比如把資料庫地址換成測試、線上地址等,而不同環境的配置,相信大家一定用
application-dev.yml、application-prod.yml……
做好了區分。
但就算提前準備了不同環境的配置,可到了切換環境時,還需要挨個服務修改
spring.profiles.active
這個值,從
dev
改成
prod、test
,然後才能使用對應的配置進行打包,可這裏有
20
個微服務啊,難道要手動改
20
次嗎?
而在父
POM
中配置了
Maven
多環境後,這時
yml
會讀取
pom.xml
中的值,來使用不同的配置檔,此時大家就只需要在
IDEA
工具的
Profiles
中,把勾點從
dev
換到
test、prod
,然後重新整理一下
Maven
,
SpringBoot
就能動態的切換配置檔,這是不是妙極了?因此,這才是
Maven
多環境的正確使用姿勢!
三、Maven私服搭建
前面叨叨絮絮說了一大堆,最後就來聊聊
Maven
私服配置,為啥需要私服呢?
大家來設想這麽個場景,假設你身在基建團隊,主要負責研發各個業務開發組的公用元件,那麽當你寫完一個元件後,為了能讓別的業務開發組用上,難道是先把程式碼打包,接著用
U
盤拷出來,給別人送過去嘛?有人說不至於,難道我不會直接發過去啊……
的確,用通訊軟體發過去也行,但問題依舊在,假設你的元件升級了,又發一遍嗎?所以,為了便於團隊協作,搭建一個遠端倉庫很有必要,寫完公用程式碼後,直接釋出到遠端倉庫,別人需要用到時,直接從遠端倉庫拉取即可,而你升級元件後,只需要再釋出一個新版本即可!
那遠端倉庫該怎麽搭建呀?這就得用到
Maven
私服技術,最常用的就是基於
Nexus
來搭建。
3.1、Nexus私服搭建指南
Nexus
是
Sonatype
公司開源的一款私服產品,大家可以先去到
Nexus官網
下載一下安裝包,
Nexus
同樣是一款解壓即用的工具,不過也要註意:
解壓的目錄中不能存在中文,否則後面啟動不起來!
解壓完成後,會看到兩個目錄:
•
nexus-x.x.x-xx
:裏面會放
Nexus
啟動時所需要的依賴、環境配置;
•
sonatype-work
:存放
Nexus
執行時的工作數據,如儲存上傳的
jar
包等。
接著可以去到:
解壓目錄/etc/nexus-default.properties
這個檔修改預設配置,預設埠號是
8081
,如果你這個埠已被使用,就可以修改一下,否則通常不需要更改。接著可以去到解壓目錄的
bin
資料夾中,開啟
cmd
終端,執行啟動命令:
nexus.exe /run nexus
初次啟動的過程會額外的慢,因為它需要初始化環境,建立工作空間、內嵌資料庫等,直到看見這句提示:
此時才算啟動成功,
Nexus
初次啟動後,會在
sonatype-work
目錄中生成一個
/nexus3/admin.password
檔,這裏面存放著你的初始密碼,預設帳號就是
admin
,在瀏覽器輸入:
http://localhost:8081
存取
Nexus
界面,接著可以在網頁上透過初始密碼登入,登入後就會讓你修改密碼,改完後就代表
Nexus
搭建成功(不過要記住,改完密碼記得重新登入一次,否則後面的操作會沒有許可權)。
3.2、Nexus私服倉庫
登入成功後,點選
Browse
會看到一些預設倉庫,這裏稍微解釋一下每個欄位的含義。
•
Name
:倉庫的名字;
•
Type
:倉庫的型別;
•
Format
:倉庫的格式;
•
Status
:倉庫的狀態;
•
URL
:倉庫的網路地址。
重點來說說倉庫的分類,總共有四種型別:
型別 | 釋義 | 作用 |
hosted
| 宿主倉庫 | 保存中央倉庫中沒有的資源,如自研元件 |
proxy
| 代理倉庫 | 配置中央倉庫,即映像源,私服中沒有時會去這個地址拉取 |
group
| 倉庫組 | 用來對宿主、代理倉庫分組,將多個倉庫組合成一個對外服務 |
virtual
| 虛擬倉庫 |
並非真實存在的倉庫,類似於
MySQL
中的檢視
|
倉庫的關系如下:
原生的
Maven
需要配置私服地址,當計畫需要的依賴,在本地倉庫沒有時,就會去到相應的宿主/遠端倉庫拉取;如果宿主倉庫也沒有,就會根據配置的代理倉庫地址,去到中央倉庫拉取,最後依次返回……。
3.3、Maven配置私服
Maven
想要使用私服,需要先修改
settings.xml
檔,我的建議是別直接改,先拷貝一份出來,接著來講講配置步驟。
①修改
settings.xml
裏的映像源配置,之前配的阿裏雲映像不能用了,改成:
<mirror>
<id>nexus-zhuzi</id>
<mirrorOf>*</mirrorOf>
<url>http://localhost:8081/repository/maven-public/</url>
</mirror>
②在私服中修改存取許可權,允許匿名使用者存取,如下:
③在
Nexus
私服中配置一下代理倉庫地址,即配置映像源:
將這個預設的中央倉庫地址,改為國內的阿裏雲映像:
http://maven.aliyun.com/nexus/content/groups/public/
改完後記得拖動到最下方,點選
Save
保存一下即可。
④在
Maven
的
settings.xml
中,配置私服的帳號密碼:
<server>
<id>zhuzi-release</id>
<username>admin</username>
<password>你的私服帳號密碼</password>
</server>
<server>
<id>zhuzi-snapshot</id>
<username>admin</username>
<password>你的私服帳號密碼</password>
</server>
這兩組配置,放到
<servers>
標簽中的任何一處即可,這裏可以先這樣配置,看不懂沒關系。
3.4、計畫配置私服
前面配置好了本地
Maven
與私服的對映關系,接著要配置計畫和私服的連線,說下流程。
①為計畫建立對應的私服倉庫,如果已有倉庫,可以直接復用,建立步驟如下:
其中唯一值得一提的就是倉庫格式,這裏有三個可選項:
•
Release
:穩定版,表示存放可以穩定使用的版本倉庫;
•
Snapshot
:快照版,代表儲存開發階段的版本倉庫;
•
Mixed
:混合版,不區分格式,表示混合儲存程式碼的倉庫。
為了規範性,我的建議是
Release、Snapshot
格式的倉庫,各自都建立一個。
②在
Maven
工程的
pom.xml
檔中,配置對應的私服倉庫地址,如下:
<!-- 配置當前工程,在私服中保存的具體位置 -->
<distributionManagement>
<repository>
<!-- 這裏對應之前 settings.xml 裏配置的server-id -->
<id>zhuzi-release</id>
<!-- 這裏代表私服倉庫的地址,大家只需要把後面的名字換掉即可 -->
<url>http://localhost:8081/repository/zhuzi-release/</url>
</repository>
<snapshotRepository>
<id>zhuzi-snapshot</id>
<url>http://localhost:8081/repository/zhuzi-snapshot/</url>
</snapshotRepository>
</distributionManagement>
③將當前計畫釋出到私服倉庫,這裏可以執行
mvn clean deploy
命令,也可以透過
IDEA
工具完成:
不過這裏有一個細節要註意,由於配置了私服上的兩個宿主倉庫,一個為穩定倉庫,另一個為快照倉庫,所以釋出時,預設會根據當前計畫的
<version>
版本結尾,來選擇上傳到相應的倉庫,例如上圖中的結尾是
SNAPSHOT
,所以會被釋出到快照倉庫,如果結尾不是這個字尾時,就會被釋出到
Release
倉庫。
當釋出完成後,大家就可以登入
Nexus
界面,找到對應的宿主倉庫,檢視相應的
jar
包資訊啦!不過還有一點要註意:你要釋出的包不能帶有上級,即不能有
parent
依賴,否則在其他人在拉取該計畫時,會找不到其父計畫而構建失敗。要解決這個問題,可以先將
parent
計畫打包並上傳至遠端倉庫,然後再釋出依賴於該
parent
計畫的子模組。
3.5、Nexus配置倉庫組
前面在說倉庫型別時,還提到過一個「倉庫組」的概念,如果你目前所處的公司,是一個大型企業,不同團隊都有著各自的宿主倉庫,而你恰恰又需要用到其他團隊的元件,這時難道需要在
pom.xml
中,將遠端倉庫地址先改為其他團隊的地址嗎?答案是不需要的,這時可以建立一個倉庫組。
大家可以看到,圖中的
Members
區域代表當前倉庫組的成員,而這些成員會按照你排列的順序,具備不同的優先級,越靠前的優先級越高。建立好倉庫組後,接著可以去配置一下倉庫組,這裏有兩種方式。
3.5.1、配置單個工程與倉庫組的對映
這種方式只需修改
pom.xml
即可:
<repositories>
<repository>
<id>zhuzi-group</id>
<!-- 配置倉庫組的地址 -->
<url>http://localhost:8081/repository/zhuzi-group/</url>
<!-- 允許從中拉取穩定版的依賴 -->
<releases>
<enabled>true</enabled>
</releases>
<!-- 也允許從中拉取快照版的依賴 -->
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>plugin-group</id>
<url>http://localhost:8081/repository/zhuzi-group/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
在上述這組配置中,配置了
<repositories>、<pluginRepositories>
兩個標簽,分別是啥意思呢?很簡單,第一個是普通依賴的倉庫組地址,第二個是外掛程式依賴的倉庫組地址,前者針對於
pom.xml
中的
<dependency>
標簽生效,後者針對
<plugin>
標簽生效。
當你透過
GAV
座標,引入一個依賴時,如果本地倉庫中沒找到,則會根據配置的倉庫組地址,去到
Nexus
私服上拉取依賴。不過因為倉庫組是由多個倉庫組成的,所以拉取時,會根據倉庫的優先級,依次搜尋相應的依賴,第一個倉庫將是最優先搜尋的倉庫。
3.5.2、配置本地Maven與倉庫組的對映
上一種配置方式,只針對於單個
Maven
工程生效,如果你所有的
Maven
工程,都需要與
Nexus
私服上的倉庫組繫結,這時就可以直接修改
settings.xml
檔,如下:
<profile>
<id>zhuzi-group</id>
<repositories>
<repository>
<id>nexus-maven</id>
<url>http://localhost:8081/repository/zhuzi-group/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus-maven</id>
<url>http://localhost:8081/repository/zhuzi-group/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
這組配置要寫在
<profiles>
標簽裏面,其他的與前一種方式沒太大區別,唯一不同的是多了一個
<updatePolicy>
標簽,該標簽的作用是指定倉庫映像的更新策略,可選項如下:
•
always
:每次需要
Maven
依賴時,都先嘗試從遠端倉庫下載最新的依賴項;
•
daily
:每天首次使用某個依賴時,從遠端倉庫中下載一次依賴項;
•
interval:X
:每隔
X
個小時,下載一次遠端倉庫的依賴,
X
只能是整數;
•
never
:僅使用本地倉庫中已經存在的依賴項,不嘗試從遠端倉庫中拉取。
Maven
工程使用依賴時,首先會從本地倉庫中尋找所需的依賴項,如果本地倉庫沒有,則從配置的遠端倉庫下載這時會根據
<updatePolicy>
策略來決定是否需要從遠端倉庫下載依賴。
不過上述這樣配置後,還無法讓配置生效,如果想要生效,還得啟用一下上述配置:
<activeProfiles>
<!-- 這裏寫前面配置的ID -->
<activeProfile>zhuzi-group</activeProfile>
</activeProfiles>
不過要記住,無論兩種方式內的哪一種,都只允許從私服上拉取依賴,如果你的某個工程,想要打包釋出到私服上,還是需要配置
3.4
階段的
<distributionManagement>
標簽。
四、Maven總結
最後,對於
Maven
計畫的命名,不同單詞最好用
-
減號分割,而不是
_
底線,畢竟
Spring、Apache……
的開源計畫,都采用這種命名方式。不過,如果你要問我:「你為啥用
_
不用
-
啊」?別問,問就是我控幾不住我寄幾啊……,更何況有句話說的好:知錯不改,善莫大焉!
到這裏,對於
Maven
常用的功能已經講完了,掌握這些知識後,玩轉
Maven
的難度應該不大,不過
Maven
的功能遠不僅如此,就光說
pom.xml
這個檔,可以配置的標簽有幾百個,本文僅講到了幾十個罷了。
個人簡介
哪咤,群友愛稱:咤哥,一個靠著熱情攀登至C站巔峰的中年男子,CSDN粉絲50萬+,2022CSDN部落格之星Top1,10年開發管理經驗,目前就職於某一線大廠,專註Java硬核幹貨分享,立誌做到Java賽道全網Top N。
喜歡的可以給個關註,關註「哪咤編程」,提高Java技能
最後歡迎大家加入哪咤的知識星球【Java學習星球】,星球中有很多獨家的幹貨內容。比如:Java後端學習路線,10萬字208道Java經典面試題,10大學習專欄,包含Java基礎、資料庫、SSM、SpringBoot、微服務、華為OD機試、演算法、最佳化、中介軟體、設計模式等系列文章。
堅持每日學習打卡,養成持續學習、持續成長的好習慣。
成功秘訣只有一個,那就是卷,督促和鞭策自己,永不放棄。
和哪咤一起學Java,陪伴學習,共同優秀。