本文介紹 maven 從安裝到上手一套操作,對新手友好,跟著練一遍可以快速入門 maven。dk 的學習素材是 《maven 實戰(zhàn)》,想要深入學習的可以看這本書。
一、安裝
1. 下載 maven
maven 官方下載頁面, window 電腦下載第二個文件,mac 電腦下載第一個文件,下載完解壓縮即可,無需安裝。接下來就是配置環(huán)境變量,這樣在終端中才可以直接運行 mvn 命令。
2. mac 下配置 maven 環(huán)境變量
編輯當前用戶目錄下的 .bash_profile 文件,在末尾添加如下內容。其中:MAVEN_HOME 的值替換成您電腦中 maven 文件所在路徑。
MAVEN_HOME=/Users/dkvirus/Documents/apache-maven-3.5.4
PATH=$MAVEN_HOME/bin:$PATH
export MAVEN_HOME
export PATH
運行 $ source .bash_profile
讓配置文件即時生效,在終端中輸入 mvn -v
如果打印出版本號說明配置成功。
3. window 下配置 maven 環(huán)境變量
window 下配置 maven 環(huán)境變量與配置 jdk 變量類似,dk 由于了剛換了機器沒法截圖,有不會的直接谷歌/百度一下吧。
二、概述
1. maven 能做什么
maven 簡化了從 項目創(chuàng)建
> 開發(fā)
> 測試
> 打包
整個流程,所有原本復雜/麻煩的操作現(xiàn)在只要在終端中敲幾個命令即可完成。
maven 以優(yōu)雅的方式處理 jar 包之間的依賴關系,這些關系都被定義在 pom.xml 中。
2. pom.xml 文件
每個 maven 項目根目錄下都會有一個 pom.xml 文件,該文件定義了項目的基本信息,包依賴關系,插件使用等情況,類似于 npm 里 package.json 的功能,pom.xml 的基本結構如下:
<?xml version="1.0" encoding="UTF-8"?>
<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>com.dkvirus</groupId>
<artifactId>hello-world</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>maven入門項目</name>
</project>
第一行 xml 頭,指定該 xml 文檔的版本為 1.0,編碼方式為 UTF-8。
第二行 project 元素,是 pom.xml 的根元素。project 元素中有一些屬性 xmlns、xmlns:xsi、xsi:schemaLocation,這些屬性叫做 命名空間
及 xsd 屬性
,用處是在開發(fā)工具中按【Ctrl + /】鍵可以提示 pom.xml 中的常用元素,而不用一個個元素的去敲。
第三行 modelVersion 元素定義當前 POM 模型的版本,maven2和maven3版本這里都填寫4.0.0;
接下來介紹的三個元素 groupId、artifactId 和 version 確定一個 jar 包在 maven 倉庫中的唯一性,就像點在空間中有x、y、z三個坐標確定位置一樣。
第四行 groupId 元素定義項目屬于哪個組,通常與項目所在的組織或公司關聯(lián)。如果是個人玩的項目,比如 dkvirus,組名就叫 com.dkvirus 吧;Npm 倉庫里確定一個包唯一坐標是通過 包名+版本號
,很顯然這種設計并不合理,像 Angular、Babel 這些大工程是不可能把所有代碼都寫到一個包里的,因此經(jīng)常會看到 @angular/animation
這種寫法,前面 @angular
標識這個包所屬組織。在這一點上,maven 就顯得預卜先知,在設計初就添加了 groupId 定義組的概念。
第五行 artifactId 元素定義當前 maven 項目在組中唯一的ID,項目名稱;
第六行 version 元素指定當前 maven 項目的版本。SNAPSHOP 是快照的意思(通常表示還在開發(fā)中),每次發(fā)布開發(fā)版本時會自動加上時間戳, 無需手動遞增版本。如果測試通過,發(fā)布正式版本,將 SNAPSHOP 去掉,如 1.0.0,沒有看到 SNAPSHOP 的包通常為正式發(fā)布的穩(wěn)定包。
第七行 name 元素聲明一個對用戶更加友好的項目名稱,可以填寫中文。
3. maven 目錄結構
maven 項目有統(tǒng)一的目錄結構:
|-- my-app
|-- src
|-- main
|-- java <= 主代碼
|-- resources <= 主代碼需要用到的資源,如配置文件等
|-- test
|-- java <= 測試代碼
|-- resources <= 測試代碼需要用到的資源
|-- target
|-- classes <= 打包之后存放路徑,.class 文件在這里哦
|-- maven-status <= 作用未知
|-- pom.xml <= 項目對象模型
根目錄下還有兩個隱藏文件,
- .classpath 定義路徑用的,定義打包哪個目錄下的文件,打包結果放到哪個目錄下;
- .project 作用未知。每個 maven 項目都有這個文件。
4. maven 本地倉庫
所有通過 maven 下載的 jar 包都會放在 maven 本地倉庫,默認位置是當前登錄用戶主目錄下的 .m2 目錄下。
代碼中經(jīng)常會看到 import org.junit.Test;
,這是導入其它 jar 包,這個 jar 可以從 .m2 目錄下找到。
三、創(chuàng)建項目
1. 創(chuàng)建目錄結構
首先按照 2.3 中介紹的 maven 默認的目錄結構手動創(chuàng)建項目 hello-world。建議別一開始就用 sts 或者 eclipse 這種 java 開發(fā)工具,找一個文本編輯器開始maven 練習吧。
如果每次創(chuàng)建新項目都要一個個手動創(chuàng)建目錄,簡直麻煩死了。maven 提供命令 $ mvn archetype:generate
可以直接生成上述目錄結構,下面創(chuàng)建第二個 maven 項目(第6.2.2小節(jié)詳細介紹,迫不及待的可以先跳轉查看)做測試時會用到該命令。
2. 定義 pom.xml
看到這個目錄下有 pom.xml 文件就知道這是一個 maven 項目,復制下面內容粘貼到 pom.xml 文件中。
項目組織為 com.dkvirus,項目名稱為 hello-world,項目版本為 0.0.1-SNAPSHOT,項目簡介為 maven 入門項目。
<?xml version="1.0" encoding="UTF-8"?>
<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>com.dkvirus</groupId>
<artifactId>hello-world</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>maven入門項目</name>
</project>
四、開發(fā)
1. 新建文件
在 /src/main/java 目錄下新建子目錄 com/dkvirus/helloworld,在該目錄下創(chuàng)建文件 HelloWorld.java(該文件完整路徑為:/src/main/java/com/dkvirus/helloworld/HelloWorld.java)。
package com.dkvirus.helloworld;
public class HelloWorld {
public String sayHello () {
return "Hello World";
}
public static void main (String[] args) {
System.out.print(new HelloWorld().sayHello());
}
}
dk 一開始學習到這里會很疑惑為什么要在 src/main/java 目錄下創(chuàng)建多層嵌套的子目錄 com/dkvirus/helloworld
,直接創(chuàng)建一個 helloworld
目錄不是更加簡潔嗎??事實證明 dk 還是 too young too simple,接著往下看會找到答案。。。(第6.2.4詳細介紹)
2. 編譯運行
代碼寫好了,接下來就編譯運行了。傳統(tǒng)做法是在終端運行 $ javac HelloWorld.java
進行編譯,使用 maven 只要執(zhí)行 $ mvn compile
即可,該命令會自動去 src/main/java 目錄下找所有 .java 結尾的文件進行編譯,從這里也可以看到 maven 規(guī)定默認目錄結構是有好處的。
1 dkvirus@localhost:hello-world$ mvn compile
2 [INFO] Scanning for projects...
3 [INFO]
4 [INFO] ----------------------< com.dkvirus:hello-world >-----------------------
5 [INFO] Building maven入門項目 0.0.1-SNAPSHOT
6 [INFO] --------------------------------[ jar ]---------------------------------
7 [INFO]
8 [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-world ---
9 [INFO] Copying 0 resource
10 [INFO]
11 [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-world ---
12 [INFO] Changes detected - recompiling the module!
13 [INFO] Compiling 1 source file to /Users/dkvirus/work/self/workspace/hello-world/target/classes
14 [INFO] ------------------------------------------------------------------------
15 [INFO] BUILD SUCCESS
16 [INFO] ------------------------------------------------------------------------
17 [INFO] Total time: 2.462 s
18 [INFO] Finished at: 2018-08-04T21:27:59+08:00
19 [INFO] ------------------------------------------------------------------------
編譯完成后可以去 /target/classes/ 目錄下可以找到 HelloWorld.class 文件。
五、測試
1. 依賴 junit 測試包
測試 Java 代碼使用 junit 測試包。傳統(tǒng)做法是去網(wǎng)上找 junit 包手動下載到本地使用,使用 maven 只需將 junit 的三個坐標添加到 pom.xml 即可,maven 會自動從 maven 的中央倉庫下載這個 jar 包。
<project>
<!-- ..... -->
<!-- 依賴配置 -->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
dependencies 元素定義當前項目依賴的所有 jar 包,其子元素 dependency 定義依賴的具體包,由 groupId、artifactId、version 唯一確定包的坐標。只需添加上述配置,保存代碼,maven 會自動下載 junit 包到本地以供使用。
Q1:maven 是從哪里下的呢??
A1:答案是:maven 中央倉庫,全世界 java 開發(fā)者都可以往這上面提交自己的 jar 包,供他人下載使用,減少重復造輪子。
Q2:怎么知道 junit 包的三個坐標呢??
A2:進入 maven 中央倉庫 這個網(wǎng)站,具體操作參考如下步驟:
1)在搜索框搜索包名,會列舉出相關包,根據(jù)組織名(groupId)和包名(artifactId)確定目標包;
2)選擇指定版本,dk 通常是通過右邊紅框中數(shù)量多少選擇具體版本;
3)下方的文本域就是要找的信息,復制到 pom.xml 文件中的 dependencies 子元素位置即可。
2. 編寫測試代碼
在 src/test/java/com/dkvirus/helloworld 目錄下新建測試文件 HelloWorldTest.java
文件。
package com.dkvirus.helloworld;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class HelloWorldTest {
@Test
public void testSayHello () {
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello World", result);
}
}
3. 編譯運行
$ mvn test
會對 src/test/java 下 *Test.java
或者 Test*.java
的文件進行編譯,編譯后的代碼放在 target 目錄下。并自動執(zhí)行測試代碼,下方會統(tǒng)計測試結果,這里可以看到測試結果是通過的。嘗試將測試代碼改為 assertEquals("Hello World2", result);
,再次執(zhí)行 $ mvn test
命令查看結果。
dkvirus@localhost:hello-world$ mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.dkvirus:hello-world >-----------------------
[INFO] Building maven入門項目 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-world ---
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-world ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/dkvirus/work/self/workspace/hello-world/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-world ---
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-world ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/dkvirus/work/self/workspace/hello-world/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-world ---
[INFO] Surefire report directory: /Users/dkvirus/work/self/workspace/hello-world/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.dkvirus.helloworld.HelloWorldTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.121 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.746 s
[INFO] Finished at: 2018-08-04T21:48:51+08:00
[INFO] ------------------------------------------------------------------------
編譯后的測試代碼可以在 /target/test-classes/ 目錄下查看到。
六、打包
1. 當前項目打成 jar 包
$ mvn package
將當前 java 項目打成 jar 包。maven 在打包前會先執(zhí)行編譯、測試操作;打包過程是將項目主代碼打包成一個名為 hello-world-0.0.1-SNAPSHOT.jar 的文件,該文件也位于 target 目錄下。打包后的 jar 包命名規(guī)則由 pom.xml 中 artifact-version.jar 的格式確定的。
dkvirus@localhost:hello-world$ mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.dkvirus:hello-world >-----------------------
[INFO] Building maven入門項目 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-world ---
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-world ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/dkvirus/work/self/workspace/hello-world/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-world ---
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-world ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/dkvirus/work/self/workspace/hello-world/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-world ---
[INFO] Surefire report directory: /Users/dkvirus/work/self/workspace/hello-world/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.dkvirus.helloworld.HelloWorldTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.1 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-world ---
[INFO] Building jar: /Users/dkvirus/work/self/workspace/hello-world/target/hello-world-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.213 s
[INFO] Finished at: 2018-08-04T21:59:01+08:00
[INFO] ------------------------------------------------------------------------
2. 讓其它項目使用該 jar 包
1)將當前 jar 包添加到 maven 本地倉庫
$ mvn install
將 hello-world 輸出的 jar 包安裝到 maven 本地倉庫中,這樣其它 maven 項目通過 import com.dkvirus.helloworld.*
就可以使用 hello-world 項目中的類了。
下面再次新建一個 maven 項目 hello-test,這次不用一個個目錄手動創(chuàng)建,直接使用 maven 命令 $ mvn archetype:generate
自動創(chuàng)建目錄結構 。
2)maven 自動生成目錄結構
|-- work
|-- hello-world
|-- hello-test
切換到 hello-world 所在的父目錄位置:work 目錄,執(zhí)行 $ mvn archetype:generate
會在該目錄下創(chuàng)建 maven 項目,這樣 hello-test 和 hello-world 就是同級關系。
接下來終端會 bulabula 輸出一大串東東,過一會看見光標不動了就進入交互模式,maven 會問一些問題需要你作答,這里以 hello-test 項目創(chuàng)建過程的交互為例進行說明。
# ------- 讓你選哪個maven原型,回車選擇默認即可 --------
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1219:
# ------- 讓你選擇maven原型的版本,回車選擇即可 --------
Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
7: 1.3
Choose a number: 7:
Define value for property 'groupId': com.dkvirus <===== 輸入 groupId,我這里輸入 com.dkvirus
Define value for property 'artifactId': hello-test <===== 輸入 artifactId,我這里輸入 hello-test
Define value for property 'version' 1.0-SNAPSHOT: : <===== 輸入包版本號,這里回車選擇默認的 1.0-SNAPSHOT
Define value for property 'package' com.dkvirus: : com.dkvirus.hellotest <==== 工程的包名,這里填寫 com.dkvirus.hellotest
Confirm properties configuration:
groupId: com.dkvirus
artifactId: hello-test
version: 1.0-SNAPSHOT
package: com.dkvirus.hellotest
Y: :
第一行讓選擇 maven 原型項目。不同原型項目目錄結構略有不同,如 maven-web 原型項目拉下來就直接是個 web 工程的目錄結構,而 maven-java 項目拉下來只是個 java 工程的目錄結構。當然你也可以定義自己的目錄結構,上傳 maven 官方倉庫,再次敲這個命令時也許會看到你自己原型項目的編號。
第二行讓選擇 maven 原型項目的版本。maven 項目可能會更新,自然就有版本的概念了,練習的話直接回車選擇默認版本即可。
3)添加 helloworld 依賴配置
修改 pom.xml 文件,在 dependencies 元素下添加 helloworld 依賴配置。
<project>
<dependencies>
...
<dependency>
<groupId>com.dkvirus</groupId>
<artifactId>hello-world</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
4)測試
修改 src/test/java 下的 APP.java 文件,替換為以下內容。這里可以看到引入了 helloworld 項目中包 com.dkvirus.helloworld 中的 HelloWorld.java 文件了。
回過頭看一下第 4.1 留下的問題:為什么創(chuàng)建多層嵌套目錄 com/dkvirus/helloworld 而不是直接創(chuàng)建子目錄 helloworld 呢??如果直接創(chuàng)建一個子目錄 helloworld,那么這里導入包語句變?yōu)?import helloworld.HelloWorld;
,想一想這樣不是很容易導致重名嗎?因此包的命名盡量以 groupId 為前綴創(chuàng)建多層嵌套目錄,組織名不至于那么容易重名。
package com.dkvirus.hellotest;
import static org.junit.Assert.*;
import org.junit.Test;
import com.dkvirus.helloworld.HelloWorld;
/**
* Unit test for simple App.
*/
public class AppTest
{
@Test
public void testSayHello () {
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello World", result);
}
}
運行 $ mvn test
查看終端輸出信息,測試通過。
dk 一開始做到這一步以為 hello-test 引用的是 maven 本地倉庫的 hello-world 項目里的包,后來發(fā)現(xiàn)并不是。
因為 hello-world 和 hello-test 都在 work 目錄下,屬于同級關系,因此 hello-test 會優(yōu)先引用同級目錄下的 hello-world 里的 HelloWorld.java 文件,而不是去 maven 本地倉庫找 jar 包。修改 work/hello-world 里的 HelloWorld.java 文件,將打印信息改成了 Hello World2
,此時運行 hello-test 項目的單元測試代碼,發(fā)現(xiàn)失敗了,這一點即可證明優(yōu)先級為同級目錄下的 hello-world 項目。
package com.dkvirus.helloworld;
public class HelloWorld {
public String sayHello () {
return "Hello World2";
}
public static void main (String[] args) {
System.out.print(new HelloWorld().sayHello());
}
}
將 work/ 目錄下的 hello-world 工程刪除或者移動一個位置,再次運行 hello-test 項目的單元測試代碼發(fā)現(xiàn)這一次是通過的,說明這一次引用的是 maven 本地倉庫里的 hello-world 里的文件。
七、總結
到這里應該對 maven 有了大概的認識了吧,即便作為前端的 dk 現(xiàn)在看公司的 java 工程也比之前清晰很多,總結一下上面的內容:
- 創(chuàng)建項目:
$ mvn archetype:generate
- 編譯代碼:
$ mvn compile
- 測試代碼:
$ mvn test
- 打包代碼:
$ mvn package
- 將當前包安裝到本地 maven 倉庫:
$ mvn install
還有一個常用的命令 $ mvn clean
會刪除 target 目錄。通常上面幾個命令執(zhí)行前都會先執(zhí)行 clean 命令,如:$ mvn clean install
就會先執(zhí)行 clean 再執(zhí)行 install,而不用分兩次命令 $ mvn clean
和 $ mvn install
執(zhí)行,實在是很方便。