Maven是Apache軟件基金會(huì)的一個(gè)開源項(xiàng)目,用來處理Java工程的自動(dòng)化構(gòu)建,它有兩個(gè)主要特征:
- 構(gòu)建
- 自動(dòng)化處理依賴關(guān)系
構(gòu)建就是將源代碼恨闪、第三方的依賴包(jar包)优质、各種資源和配置打包成為一個(gè)可執(zhí)行文件(如war包)的過程。現(xiàn)在的高級(jí)IDE研叫,比如IDEA,Eclipse都有自動(dòng)化構(gòu)建的功能璧针,我們只需要點(diǎn)一下run
按鈕蓝撇,IDE就會(huì)自動(dòng)的幫我們構(gòu)建項(xiàng)目,并部署IDE內(nèi)置的容器(Tomcat)中陈莽,供我們預(yù)覽和調(diào)試渤昌。
既然有了這些高級(jí)IDE,為什么Maven還存在呢走搁?實(shí)際上Maven項(xiàng)目建立之初独柑,還沒有這些高級(jí)的IDE,關(guān)于自動(dòng)構(gòu)建工具的發(fā)展史如下:
其中Maven取代Ant的時(shí)候私植,高級(jí)的IDE還沒有出現(xiàn)忌栅,現(xiàn)在即使有這些高級(jí)的IDE,它也僅僅是解決了在開發(fā)階段自動(dòng)構(gòu)建的問題曲稼,并沒有解決項(xiàng)目的依賴問題索绪,更何況是在DevOps中的持續(xù)交互和持續(xù)集成。
1 Maven安裝與配置
要運(yùn)行Maven需要下載JDK贫悄,這里不做JDK的安裝的介紹瑞驱。
到Maven官網(wǎng)下載Maven后,解壓到指定的目錄,這里我放在了/usr/local/maven
目錄下窄坦。然后做Mac系統(tǒng)環(huán)境變量配置唤反,在/etc/paths.d
目錄下新建maven文件凳寺,寫入maven目錄的路徑,需要切換到root用戶彤侍。
echo "/usr/local/maven/bin" > /etc/paths.d/maven
使用mvn -v
查看Maven的信息:
mvn -v
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T02:41:47+08:00)
Maven home: /usr/local/maven
Java version: 1.8.0_151, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: "mac os x", version: "10.14", arch: "x86_64", family: "mac"
使用tree
命令查看Maven的目錄結(jié)構(gòu):
# Maven的目錄結(jié)構(gòu)
tree -L 1 /usr/local/maven/
/usr/local/maven/
├── LICENSE
├── NOTICE
├── README.txt
├── bin
├── boot
├── conf
└── lib
比較重要的是conf/setting.xml
文件肠缨,它是Maven的配置文件,有以下幾個(gè)標(biāo)簽需要注意:
-
localRepository 表示的Maven本地倉(cāng)庫(kù)的位置盏阶,默認(rèn)值是
${user.home}/.m2/repository
晒奕,及當(dāng)前用戶根目錄下。倉(cāng)庫(kù)的概念放在后面說名斟,這里大概知道是用來存放從網(wǎng)上下載下來的第三方j(luò)ar包的吴汪。 - mirrors 表示要從哪里下載第三方j(luò)ar,及中央倉(cāng)庫(kù)的鏡像(后面說)蒸眠,Maven的中央倉(cāng)庫(kù)在國(guó)外,一般為了提高下載速度杆融,我們都會(huì)配置國(guó)內(nèi)的鏡像楞卡,這里我配置了阿里云的鏡像,也可以配置多個(gè)鏡像脾歇。
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
2 構(gòu)建過程
Maven的構(gòu)建過程大致分為以下步驟:
- Clean 清空編譯結(jié)果蒋腮,為下一次編譯做準(zhǔn)備
- Compile 編譯主程序源文件,Maven將源文件分為主程序源文件和測(cè)試程序源文件藕各,他們放在不同的目錄
- Test 編譯測(cè)試程序源文件
- Package 將主程序編譯后的class文件池摧、資源文件以及配置文件,打包為對(duì)應(yīng)的類型(jar包或war包激况,需要在pom.xml中設(shè)置)
- Install 將打包后的文件按照一定規(guī)則(groupId+artifactId的方式)安裝到本地倉(cāng)庫(kù)中
- Deploy 部署程序
構(gòu)建過程是按上面的順序依次進(jìn)行的作彤,我們可以通過IDE或CLI選擇執(zhí)行到某一步。比如我們選擇執(zhí)行package
打包這一步乌逐,Maven會(huì)先執(zhí)行package
前面的所有步驟竭讳,最后才做打包這個(gè)步驟,也就是說浙踢,不管我們要執(zhí)行哪一個(gè)步驟绢慢,Maven都會(huì)從Clean
開始,順序往后執(zhí)行到我們選擇的那一步洛波。
通過CLI的方式執(zhí)行構(gòu)建命令時(shí)胰舆,只需在項(xiàng)目的根目錄輸入mvn后面加要執(zhí)行到的步驟即可。例如蹬挤,要執(zhí)行到打包(package)這一步缚窿,可在項(xiàng)目的根目錄中輸入以下命令:
mvn package
3 Maven工程目錄結(jié)構(gòu)
Maven工程采用約定大于配置(Convention over Configuration)的設(shè)計(jì),及源文件放哪個(gè)目錄焰扳,測(cè)試文件放哪個(gè)目錄是約定好了的滨攻,如果不按這個(gè)約定存放够话,那么Maven會(huì)不能正確的構(gòu)建這個(gè)項(xiàng)目。
maven_webApp
├── pom.xml
└── src
├── main
│ ├── java
│ ├── resrouce
│ └── webapp
│ ├── WEB-INF
│ │ └── web.xml
│ └── index.jsp
└── test
├── java
└── resource
上面是Maven WebApp的一個(gè)目錄結(jié)構(gòu)光绕,在根目錄下有一個(gè)src
目錄和pom.xml
文件女嘲,pom.xml
是Maven工程的標(biāo)志,可以簡(jiǎn)單的理解為只有有了pom.xml
文件诞帐,這個(gè)工程才是Maven工程欣尼。
src
目錄下面有main
目錄和test
目錄,分別用來存放主程序和測(cè)試程序停蕉,其下面又分為java
和resource
目錄愕鼓,java
目錄用來存放源碼文件低缩,resource
目錄用來存放資源文件望门。如果是webapp的工程,建議(非強(qiáng)制約定)在main
目錄下添加webapp的目錄呆万,用來存放于web相關(guān)的資源蚓挤。
4 倉(cāng)庫(kù)與坐標(biāo)
4.1 倉(cāng)庫(kù)
倉(cāng)庫(kù)是用來存放東西的磺送,方便我們引用,這些東西大致可以分類3類:
-
存放Maven構(gòu)建過程需要的plugin灿意,Maven核心程序僅定義了構(gòu)建的接口估灿,具體的實(shí)現(xiàn)是由單獨(dú)的plugin完成的。比如pom.xml文件中定義如下的plugin插件
<build> <pluginManagement> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> </pluginManagement> </build>
maven-compiler-plugin
是Maven的編譯插件缤剧,在執(zhí)行mvn compile
命令時(shí)馅袁,由Maven核心程序調(diào)用。它存放在~/.m2/repository/org/apache/maven/plugins/maven-compiler-plugin
目錄中荒辕。 存放通過
mvn install
命令安裝的本地工程存放第三方的jar包
倉(cāng)庫(kù)按位置又可以分為:本地倉(cāng)庫(kù)汗销,私有倉(cāng)庫(kù),中央倉(cāng)庫(kù)以及中央倉(cāng)庫(kù)鏡像抵窒。比如大溜,我們需要引用一個(gè)jar包,Maven會(huì)先在本地倉(cāng)庫(kù)查找估脆,如果沒有钦奋,就到私有倉(cāng)庫(kù)中找,私有倉(cāng)庫(kù)有該包疙赠,將該jar包下載到本地倉(cāng)庫(kù)付材;如果私有倉(cāng)庫(kù)沒有,就從中央倉(cāng)庫(kù)(鏡像)中下載該包到私有倉(cāng)庫(kù)和本地倉(cāng)庫(kù)圃阳。這里私有倉(cāng)庫(kù)并不是必須的厌衔,一般有條件的企業(yè)會(huì)通過Nexus等軟件自行搭建一個(gè)私有倉(cāng)庫(kù),以方便企業(yè)內(nèi)部開發(fā)者對(duì)jar包的引用捍岳。中央倉(cāng)庫(kù)的鏡像的目錄是提升jar包的下載速度富寿。
4.2 坐標(biāo)
坐標(biāo)是jar包的元數(shù)據(jù)睬隶,用來描述jar包在倉(cāng)庫(kù)中的位置。包括groupId組織名稱页徐,artifactId項(xiàng)目名稱苏潜,version信息等,通過這三個(gè)屬性变勇,Maven就能準(zhǔn)確的找到該包恤左。比如,程序中要引用mysql驅(qū)動(dòng)搀绣,可以在pom.xml文件中添加如下的配置(依賴)飞袋,該配置中包含了坐標(biāo)信息,Maven將通過這個(gè)坐標(biāo)找到對(duì)應(yīng)的jar包链患。
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
5 依賴
我們創(chuàng)建了一個(gè)項(xiàng)目A巧鸭,如果它要完成某些工作,需要引入項(xiàng)目B麻捻,那么我們就說A依賴于B纲仍。在沒有用Maven時(shí),我們需要手動(dòng)的將B復(fù)制到項(xiàng)目A中芯肤,并添加引用關(guān)系。使用Maven后压鉴,我們只需在pom.xml文件中做如下配置即可崖咨,Maven會(huì)為我們下載對(duì)應(yīng)坐標(biāo)的包,以及添加引用油吭。
<dependency>
<groupId>組織名稱</groupId>
<artifactId>項(xiàng)目名稱</artifactId>
<version>版本號(hào)</version>
</dependency>
如果B又依賴C击蹲,那么A就間接依賴C。為了區(qū)分間接依賴婉宰,我們也可以說A是直接依賴B歌豺,如下圖所示:
Maven會(huì)將B加入工程A中,而項(xiàng)目C作為A的間接依賴心包,也為加入到A中类咧,我們叫這種情況為依賴傳遞。
5.1 依賴傳遞原則
依賴傳遞的原則:就近原則+聲明先后原則蟹腾。
考慮如下情況:
項(xiàng)目C有兩個(gè)版本痕惋,分別是V1和V2版本。項(xiàng)目A直接依賴B和C V2版本娃殖,B直接依賴C的V1版本值戳,此時(shí)Maven會(huì)通過就近原則添加C V2版本。就近原則基本是按步長(zhǎng)來計(jì)算的炉爆,那什么是步長(zhǎng)呢堕虹?比如A到C V2只要一步卧晓,而A到C V1需要通過B,及A到B為一步赴捞,B到C V1是一步逼裆,總計(jì)2步,所以A到C V2的步長(zhǎng)小于A到CV1螟炫,此時(shí)Maven會(huì)添加C V2到項(xiàng)目中波附。
如果A到達(dá)C的兩個(gè)版本的步長(zhǎng)是相同的,Maven會(huì)按pom中的定義的先后順序加入對(duì)應(yīng)的依賴昼钻,先定義的會(huì)加入到A的依賴中掸屡。
上面兩種情況,A到C的步長(zhǎng)是相同的然评,Maven會(huì)更加定義的先后順序添加C仅财,因?yàn)镃1比C2先定義,所以Maven不會(huì)添加C2版本碗淌。
5.2 依賴排除
依賴排除是通過配置pom.xml文件盏求,實(shí)現(xiàn)對(duì)間接依賴的排除,這個(gè)不是常用亿眠。具體的做法是在dependency
標(biāo)簽中使用exclusions
標(biāo)簽
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.22.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
我們知道spring-core
模塊依賴commons-logging
模塊做日志輸出碎罚,如果我們不希望加載commons-logging
,可以使用上面的配置將logging排除在依賴之外纳像。
5.3 依賴的范圍
依賴的范圍和構(gòu)建的階段(過程)是密不可分的荆烈,Maven使用scope
標(biāo)簽來標(biāo)識(shí)依賴的范圍,
compile - scope的默認(rèn)值竟趾,標(biāo)識(shí)依賴在整個(gè)生命周期都適用憔购。
-
provided - 僅在編譯和測(cè)試階段有效,它不做依賴的傳遞岔帽。比如我們?cè)陂_發(fā)web app時(shí)玫鸟,在開發(fā)階段我們需要引用servlet-api的jar包,而當(dāng)我們部署app到tomcat容器時(shí)犀勒,tomcat容器為我們提供了servlet-api的jar包屎飘,如果我們將引用的servlet-api打包到war包中,就會(huì)與容器的jar沖突贾费。此時(shí)我們可以標(biāo)記servlet-api為provided枚碗,使servlet-api這個(gè)jar包只在編譯和測(cè)試階段有效,而我們?cè)诖虬?xiàng)目時(shí)铸本,將該jar排除在外肮雨,以解決沖突。
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
runtime - 這個(gè)scope的范圍表示依賴不在參與編譯階段箱玷,這個(gè)依賴是服務(wù)于執(zhí)行階段的怨规,這個(gè)很少用到陌宿。
test - 僅在編譯和測(cè)試階段有效,不參與打包等其他階段波丰,標(biāo)記為test的依賴不會(huì)做依賴傳遞壳坪。
system - 與
provided
相似, 只是這個(gè)依賴包不會(huì)從倉(cāng)庫(kù)中查找掰烟,這個(gè)也很少用到爽蝴。
源碼編譯階段 | 測(cè)試源碼編譯階段 | 打包階段 | 部署階段 | |
---|---|---|---|---|
compile | ? | ? | ? | ? |
test | ? | ? | - | - |
provided | ? | ? | - | - |
注意test和provided的區(qū)別:test在源碼編譯和測(cè)試源碼編譯都會(huì)參與,這個(gè)依賴包只是做測(cè)試用的纫骑,后面打包部署的時(shí)候不會(huì)添加該依賴包蝎亚,容器中也不會(huì)提供該依賴包;provided在源碼編譯和測(cè)試源碼編譯都會(huì)參與先馆,打包的時(shí)候发框,也不會(huì)加入該依賴包。但是煤墙,一般是tomcat等容器會(huì)提供該依賴包梅惯。
6 POM
上面零散的說了些POM的內(nèi)容,并沒有提到POM的概念仿野,這里補(bǔ)充一下铣减,POM(Project Object Model)翻譯過來是項(xiàng)目對(duì)象模型。我們可以將POM項(xiàng)目對(duì)象模型分開理解脚作,一個(gè)Maven工程就是一個(gè)項(xiàng)目對(duì)象葫哗,比如,一個(gè)Maven的java工程和一個(gè)Maven的web工程鳖枕,他們都是一個(gè)獨(dú)立的項(xiàng)目對(duì)象魄梯。知道了項(xiàng)目對(duì)象后桨螺,我們對(duì)項(xiàng)目對(duì)象進(jìn)行抽象宾符,建立一個(gè)模型,用這個(gè)模型來描述這個(gè)Maven工程灭翔,我們這個(gè)模型是POM項(xiàng)目對(duì)象模型魏烫。
我們抽象出來的模型總的有個(gè)地方存,存在個(gè)模型的文件就是pom.xml肝箱,也可說POM的具體表現(xiàn)形式就是pom.xml哄褒。前面提到的工程的坐標(biāo),用到的插件煌张,依賴的包等都是用來描述Maven工程的呐赡,這些信息就是模型的一部分,所以我們定義在pom.xml文件中骏融。除了上面提到的模型信息外链嘀,下面在介紹幾個(gè)模型的抽象萌狂。
6.1 Properties
可以把properties
想象為POM的自定義占位符,用來替換POM中的硬編碼怀泊。下面的例子使用properties中自定義的標(biāo)簽spring.version替換spring-webmvc的版本號(hào)茫藏。
<properties>
<spring.version>4.3.22.RELEASE</spring.version>
</properties>
<dependenies>
<dependeny>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependeny>
</dependenies>
6.2 更多參考
Apache官網(wǎng)上列出POM模型的更多參考。連接地址