本文介绍 maven 从安装到上手一套操作,对新手友好,跟着练一遍可以快速入门 maven。dk 的学习素材是 《maven 实战》,想要深入学习的可以看这本书。
一、安装
1. 下载 maven
maven 官方下载页面, window 电脑下载第二个文件,mac 电脑下载第一个文件,下载完解压缩即可,无需安装。接下来就是配置环境变量,这样在终端中才可以直接运行 mvn 命令。
2. mac 下配置 maven 环境变量
编辑当前用户目录下的 .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 环境变量
window 下配置 maven 环境变量与配置 jdk 变量类似,dk 由于了刚换了机器没法截图,有不会的直接谷歌/百度一下吧。
二、概述
1. maven 能做什么
maven 简化了从 项目创建
> 开发
> 测试
> 打包
整个流程,所有原本复杂/麻烦的操作现在只要在终端中敲几个命令即可完成。
maven 以优雅的方式处理 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 属性
,用处是在开发工具中按【Ctrl + /】键可以提示 pom.xml 中的常用元素,而不用一个个元素的去敲。
第三行 modelVersion 元素定义当前 POM 模型的版本,maven2和maven3版本这里都填写4.0.0;
接下来介绍的三个元素 groupId、artifactId 和 version 确定一个 jar 包在 maven 仓库中的唯一性,就像点在空间中有x、y、z三个坐标确定位置一样。
第四行 groupId 元素定义项目属于哪个组,通常与项目所在的组织或公司关联。如果是个人玩的项目,比如 dkvirus,组名就叫 com.dkvirus 吧;Npm 仓库里确定一个包唯一坐标是通过 包名+版本号
,很显然这种设计并不合理,像 Angular、Babel 这些大工程是不可能把所有代码都写到一个包里的,因此经常会看到 @angular/animation
这种写法,前面 @angular
标识这个包所属组织。在这一点上,maven 就显得预卜先知,在设计初就添加了 groupId 定义组的概念。
第五行 artifactId 元素定义当前 maven 项目在组中唯一的ID,项目名称;
第六行 version 元素指定当前 maven 项目的版本。SNAPSHOP 是快照的意思(通常表示还在开发中),每次发布开发版本时会自动加上时间戳, 无需手动递增版本。如果测试通过,发布正式版本,将 SNAPSHOP 去掉,如 1.0.0,没有看到 SNAPSHOP 的包通常为正式发布的稳定包。
第七行 name 元素声明一个对用户更加友好的项目名称,可以填写中文。
3. maven 目录结构
maven 项目有统一的目录结构:
|-- 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 目录下。
代码中经常会看到 import org.junit.Test;
,这是导入其它 jar 包,这个 jar 可以从 .m2 目录下找到。
三、创建项目
1. 创建目录结构
首先按照 2.3 中介绍的 maven 默认的目录结构手动创建项目 hello-world。建议别一开始就用 sts 或者 eclipse 这种 java 开发工具,找一个文本编辑器开始maven 练习吧。
如果每次创建新项目都要一个个手动创建目录,简直麻烦死了。maven 提供命令 $ mvn archetype:generate
可以直接生成上述目录结构,下面创建第二个 maven 项目(第6.2.2小节详细介绍,迫不及待的可以先跳转查看)做测试时会用到该命令。
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>
四、开发
1. 新建文件
在 /src/main/java 目录下新建子目录 com/dkvirus/helloworld,在该目录下创建文件 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 目录下创建多层嵌套的子目录 com/dkvirus/helloworld
,直接创建一个 helloworld
目录不是更加简洁吗??事实证明 dk 还是 too young too simple,接着往下看会找到答案。。。(第6.2.4详细介绍)
2. 编译运行
代码写好了,接下来就编译运行了。传统做法是在终端运行 $ javac HelloWorld.java
进行编译,使用 maven 只要执行 $ mvn compile
即可,该命令会自动去 src/main/java 目录下找所有 .java 结尾的文件进行编译,从这里也可以看到 maven 规定默认目录结构是有好处的。
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 测试包。传统做法是去网上找 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 开发者都可以往这上面提交自己的 jar 包,供他人下载使用,减少重复造轮子。
Q2:怎么知道 junit 包的三个坐标呢??
A2:进入 maven 中央仓库 这个网站,具体操作参考如下步骤:
1)在搜索框搜索包名,会列举出相关包,根据组织名(groupId)和包名(artifactId)确定目标包;
2)选择指定版本,dk 通常是通过右边红框中数量多少选择具体版本;
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 目录下。并自动执行测试代码,下方会统计测试结果,这里可以看到测试结果是通过的。尝试将测试代码改为 assertEquals("Hello World2", result);
,再次执行 $ 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 在打包前会先执行编译、测试操作;打包过程是将项目主代码打包成一个名为 hello-world-0.0.1-SNAPSHOT.jar 的文件,该文件也位于 target 目录下。打包后的 jar 包命名规则由 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,这次不用一个个目录手动创建,直接使用 maven 命令 $ mvn archetype:generate
自动创建目录结构 。
2)maven 自动生成目录结构
|-- work
|-- hello-world
|-- hello-test
切换到 hello-world 所在的父目录位置:work 目录,执行 $ mvn archetype:generate
会在该目录下创建 maven 项目,这样 hello-test 和 hello-world 就是同级关系。
接下来终端会 bulabula 输出一大串东东,过一会看见光标不动了就进入交互模式,maven 会问一些问题需要你作答,这里以 hello-test 项目创建过程的交互为例进行说明。
# ------- 让你选哪个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 留下的问题:为什么创建多层嵌套目录 com/dkvirus/helloworld 而不是直接创建子目录 helloworld 呢??如果直接创建一个子目录 helloworld,那么这里导入包语句变为 import helloworld.HelloWorld;
,想一想这样不是很容易导致重名吗?因此包的命名尽量以 groupId 为前缀创建多层嵌套目录,组织名不至于那么容易重名。
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 项目里的包,后来发现并不是。
因为 hello-world 和 hello-test 都在 work 目录下,属于同级关系,因此 hello-test 会优先引用同级目录下的 hello-world 里的 HelloWorld.java 文件,而不是去 maven 本地仓库找 jar 包。修改 work/hello-world 里的 HelloWorld.java 文件,将打印信息改成了 Hello World2
,此时运行 hello-test 项目的单元测试代码,发现失败了,这一点即可证明优先级为同级目录下的 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 项目的单元测试代码发现这一次是通过的,说明这一次引用的是 maven 本地仓库里的 hello-world 里的文件。
七、总结
到这里应该对 maven 有了大概的认识了吧,即便作为前端的 dk 现在看公司的 java 工程也比之前清晰很多,总结一下上面的内容:
- 创建项目:
$ mvn archetype:generate
- 编译代码:
$ mvn compile
- 测试代码:
$ mvn test
- 打包代码:
$ mvn package
- 将当前包安装到本地 maven 仓库:
$ mvn install
还有一个常用的命令 $ mvn clean
会删除 target 目录。通常上面几个命令执行前都会先执行 clean 命令,如:$ mvn clean install
就会先执行 clean 再执行 install,而不用分两次命令 $ mvn clean
和 $ mvn install
执行,实在是很方便。