@[TOC]
什么是Maven
Maven首先是一种最佳实践的模式,通过提供一些标准来提高效率。Maven本质是项目管理的工具,它能提供:
- Builds 构建管理
- Reporting 报告管理
- Denpendencies 依赖管理
- SCMs 软件配置管理
- Releases 发版管理
-
Distribution 分发管理
如何创建Maven工程
创建Maven工程会使用到原型(Archetype)机制,原型机制可以快速创建模板化的工程。
以下命令使用Maven自带的原型创建工程:mvn -B archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4执行结束后会创建工程目录my-app,目录中还包含pom.xml文件:
4.0.0 com.mycompany.app my-app 1.0-SNAPSHOT my-app http://www.example.com UTF-8 1.7 1.7 junit junit 4.11 test ... lots of helpful plugins pom.xml 是项目对象模型(POM Project Object Model),其包含了项目的所有信息:
- project:pom.xml的最外层元素
- modelVersion: pom使用的对象模型的版本
- groupId:创建项目的组织机构,通常会是组织机构的域名,例如org.apache.maven.plugins
- artifactid: 项目生成的成果物的名称,通常会是jar文件,通常生成文件的命名规则为
- . ,例如myapp-1.0.jar - version: 项目生成的成果物的版本
- name: Maven项目的展示名称,这个名称通常是显示在文档中的
- url: 项目的网站地址
- properties: 定义可以在POM中任意位置使用的变量
- dependencies: 项目依赖
- build: 定义项目目录结构和管理插件
上面创建的项目会有以下目录结构:
my-app
|-- pom.xml
`-- src
|-- main
| `-- java
| `-- com
| `-- mycompany
| `-- app
| `-- App.java
`-- test
`-- java
`-- com
`-- mycompany
`-- app
`-- AppTest.java
可以看到,根据原型创建的项目包含pom.xml,应用源代码目录,测试源代码目录,这也是Maven工程的标准布局,如果是手工创建工程,也要依据此机构进行创建,这是Maven的约定。
编译应用源代码
进入到包含pom.xml的目录,执行以下命令:
mvn compile
会得到以下输出:
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /my-app/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /my-app/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.899 s
[INFO] Finished at: 2020-07-12T11:31:54+01:00
[INFO] ---------------------------------------------------------------------
第一次执行这个命令,Maven需要下载所有的插件以及项目中的依赖。
编译后的文件存储于${basedir}/target/classes,这是Maven的另一个标准约定
如何编译测试源代码和运行单元测试
编译单元测试用例源代码并执行
mvn test
命令之后输出如下:
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /my-app/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ my-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /my-app/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ my-app ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /my-app/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ my-app ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mycompany.app.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.025 s - in com.mycompany.app.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.881 s
[INFO] Finished at: 2020-07-12T12:00:33+01:00
[INFO] ---------------------------------------------------------------------
在输出内容中可以看到:
- Maven下载了更多的依赖,这些是执行测试所需要的的依赖和插件
- 在编译执行测试之前,Maven会先编译主代码
如果只是想编译测试源代码而不执行,可以用以下命令:
mvn test-compile
如何创建JAR并安装到本地资源库
创建JAR执行以下命令:
mvn package
生成的JAR文件在目录${basedir}/target
如果想将刚刚生成的JAR安装进行本地资源库(默认路径${user.home}/.m2/repository),执行以下命令:
mvn install
输出信息为:
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app ---
...
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ my-app ---
...
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ my-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ my-app ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mycompany.app.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.025 s - in com.mycompany.app.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ my-app ---
[INFO] Building jar: /my-app/target/my-app-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ my-app ---
[INFO] Installing /my-app/target/my-app-1.0-SNAPSHOT.jar to /com/mycompany/app/my-app/1.0-SNAPSHOT/my-app-1.0-SNAPSHOT.jar
[INFO] Installing /my-app/pom.xml to /com/mycompany/app/my-app/1.0-SNAPSHOT/my-app-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.678 s
[INFO] Finished at: 2020-07-12T12:04:45+01:00
[INFO] ------------------------------------------------------------------------
注意执行测试的surefire插件是依照命名约定来定位测试文件的,默认的规则为:
- */Test.java
- */Test.java
- */TestCase.java
默认排除的文件命名:
- */AbstractTest.java
- */AbstractTestCase.java
目前已经介绍了Maven工程的典型流程,包括创建、编译、测试、打包、安装,这些步骤也是大部分Maven项目主要使用的,这些所有步骤都由POM来驱动,如果使用Ant来实现这些功能,则脚本文件将会复杂很多。
Maven有很多有用的开箱即用的插件,这里我们列举一个插件,它也是Maven的优点之一:创建工程描述文档站点:
mvn site
还有大量的独立目标,例如:
mvn clean
该命令会清空target目录,通常在构建工程之前执行,这样能保证工程文件都是最新的。
什么是快照版本
在pom.xml的版本标签中包含-SNAPSHOT,就是快照版本
...
my-app
...
1.0-SNAPSHOT
Maven Quick Start Archetype
...
快照版本是指开发分支上的最新代码,不保证代码是稳定的,只要发布版本才会保证是不变且稳定的。
换言之,快照版本就是在发布版本之前的开发版本。
在版本释放的过程中,版本号会去掉-SNAPSHOT后缀,x.y-SNAPSHOT 变为 x.y,版本释放也会使快照版本号增加为x.(y+1)-SNAPSHOT,如1.0-SNAPSHOT 释放为 1.0,最新的开发版本为1.1-SNAPSHOT
如何使用插件
如果要自定义Maven工程的构建时,就需要使用插件或者配置插件
例如,我们要设置Java编译器为JDK5,以下是POM的例子:
org.apache.maven.plugins
maven-compiler-plugin
3.3
1.5
1.5
在Maven中插件就像一个依赖,从某些方面看插件就是依赖,插件会自动下载并运行,用户也可以指定插件的版本,默认的版本采用最新
configuration 元素会将配置的要素传递给compiler 插件的所有目标,在上面的例子中,compiler 插件已经作为构建流程的一部分,这个配置仅仅是改变了一些信息。也可以为流程增加新的目标。
如何为JAR添加资源
添加资源不需要更改POM,Maven是依赖标准目录布局,也就是说将资源文件按照约定放置在指定目录中,Maven就会将资源打包进JAR中
以下的目录机构中展示了资源文件的位置 ${basedir}/src/main/resources ,放置在其中的任何目录或者文件都会被打包进JAR
my-app
|-- pom.xml
`-- src
|-- main
| |-- java
| | `-- com
| | `-- mycompany
| | `-- app
| | `-- App.java
| `-- resources
| `-- META-INF
| `-- application.properties
`-- test
`-- java
`-- com
`-- mycompany
`-- app
`-- AppTest.java
在上面的例子中工程中有一个目录META-INF,其中包含application.properties,如果将这个工程的打包JAR文件解压,你会看到以下文件结构:
|-- META-INF
| |-- MANIFEST.MF
| |-- application.properties
| `-- maven
| `-- com.mycompany.app
| `-- my-app
| |-- pom.properties
| `-- pom.xml
`-- com
`-- mycompany
`-- app
`-- App.class
${basedir}/src/main/resources 目录中的内容会在JAR的根目录下,application.properties 文件在 META-INF目录中,也可以看到还有其他的文件META-INF/MANIFEST.MF、pom.xml、pom.properties,这些是Maven生成JAR文件的标准描述文件,你也可以自定义manifest信息。pom.xml和pom.properties也被打包进JAR文件了,这样你可以在有需求的场景去使用,一个最简单的场景就是查看JAR文件的版本,pom.properties如下:
#Generated by Maven
#Tue Oct 04 15:43:21 GMT-05:00 2005
version=1.0-SNAPSHOT
groupId=com.mycompany.app
artifactId=my-app
测试资源的约定路径为${basedir}/src/test/resources
my-app
|-- pom.xml
`-- src
|-- main
| |-- java
| | `-- com
| | `-- mycompany
| | `-- app
| | `-- App.java
| `-- resources
| `-- META-INF
| |-- application.properties
`-- test
|-- java
| `-- com
| `-- mycompany
| `-- app
| `-- AppTest.java
`-- resources
`-- test.properties
在测试用例中,你可以用以下代码获取资源:
// Retrieve resource
InputStream is = getClass().getResourceAsStream( "/test.properties" );
// Do something with the resource
如何过滤资源文件
有时资源文件中会包含一些只在构建期间使用的信息,为了实现这个需求,Maven中支持在资源文件变量引用的语法${
Maven过滤资源,只需要将在pom.xml中filtering 属性值设置为true
4.0.0
com.mycompany.app
my-app
1.0-SNAPSHOT
jar
Maven Quick Start Archetype
http://maven.apache.org
junit
junit
4.11
test
src/main/resources
true
上面build中的resources属性默认是可以缺省的,因为Maven已经定了目录等默认值,如果指定filtering为true,则需要显示的指定
在引用pom.xml中定义的变量时,可以直接使用xml便签名引用标签内的值,pom的跟别名为project,所以${project.name}为工程名,${project.version}为工程版本,${project.build.finalName}为打包文件名,等等。注意pom中的元素都由默认值,所以可以不必显示的定义。同样的,用户setting.xml可以通过settings.的方式引用,例如${settings.localRepository}为本地资源库的路径
我们src/main/resources目录下的application.properties增加以下内容:
application.name=${project.name}
application.version=${project.version}
执行以下命令(该命令为Maven复制和过滤资源文件的过程)
mvn process-resources
target/classes目录下的application.properties会被过滤为以下:
application.name=Maven Quick Start Archetype
application.version=1.0-SNAPSHOT
如何使用外部依赖
pom.xml中dependencies 元素列举了工程所需的所有依赖
4.0.0
com.mycompany.app
my-app
1.0-SNAPSHOT
jar
Maven Quick Start Archetype
http://maven.apache.org
junit
junit
4.11
test
对于任何一个外部依赖,都需要定义至少4项信息:groupId,artifactId,version,scope,groupId,artifactId,version是依赖的工程属性,scope指定了当前工程如何使用这个依赖,它的值可以是compile, test, and runtime
有了这些信息,工程就能引用依赖,那么这些依赖是从哪里来的?
Maven首先会检索本地资源库,默认路径是${user.home}/.m2/repository,在之前的章节里我们使用mvn install在本地资源库安装了my-app-1.0-SNAPSHOT.jar, 那么其他工程就可以引用了
com.mycompany.app
my-other-app
...
...
com.mycompany.app
my-app
1.0-SNAPSHOT
compile
那么在其他地方构建的依赖怎么引用?他们如何下载到我本地资源库?如果引用的依赖本地资源库不存在,Maven会从远程资源库下载至本地资源库,你会发现在首次使用maven构建工程时会下载很多东西,下载的东西通常是构建工程使用的插件,默认的远程资源库为 https://repo.maven.apache.org/maven2/.,你也可以设置自定义的远程仓库。
例如我们想添加记录日志的依赖,在Maven中央仓库中的目录为 /maven2/log4j/log4j,目录中有文件maven-metadata.xml,其内容为:
log4j
log4j
1.1.3
1.1.3
1.2.4
1.2.5
1.2.6
1.2.7
1.2.8
1.2.11
1.2.9
1.2.12
通过这个文件我们可以看到groupId 是log4j,artifactId是log4j,我们看到有很多版本可以选择,我们使用最新版本1.2.12。此外,我们可以看到每个版本的log4j都由一个单独的目录,目录内部包含jar文件以及pom.xml以及另外的 maven-metadata.xml,其中的md5文件包含了这些文件的hash码,你可以用来认证这个库或者识别出正在使用的版本。
4.0.0
com.mycompany.app
my-app
1.0-SNAPSHOT
jar
Maven Quick Start Archetype
http://maven.apache.org
junit
junit
4.11
test
log4j
log4j
1.2.12
compile
敲入命令mvn compile,可以看到maven开始下载log4j
如何发布自己的jar到远程仓库
想要发布jar到外部的资源库,需要在pom.xml中配置资源库的url以及在settings.xml.配置资源库的身份认证信息
下面的例子中使用了 scp 和 username/password 身份认证:
4.0.0
com.mycompany.app
my-app
1.0-SNAPSHOT
jar
Maven Quick Start Archetype
http://maven.apache.org
junit
junit
4.11
test
org.apache.codehaus.plexus
plexus-utils
1.0.4
src/main/filters/filters.properties
src/main/resources
true
mycompany-repository
MyCompany Repository
scp://repository.mycompany.com/repository/maven2
...
mycompany-repository
jvanzyl
/path/to/identity (default is ~/.ssh/id_dsa)
my_key_passphrase
...
如何创建其他类型的工程
使用以下命令创建一个新的工程
mvn archetype:generate \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-webapp \
-DgroupId=com.mycompany.app \
-DartifactId=my-webapp
生成的pom.xml如下:
4.0.0
com.mycompany.app
my-webapp
1.0-SNAPSHOT
war
junit
junit
4.11
test
my-webapp
mvn package
# 如何同时构建多个工程
Maven可以处理多模块,首先根据之前构建的两个工程的父目录中创建pom.xml,目录结构如下:
+- pom.xml
+- my-app
| +- pom.xml
| +- src
| +- main
| +- java
+- my-webapp
| +- pom.xml
| +- src
| +- main
| +- webapp
pom.xml的内容为:
```xml
在my-webapp/pom.xml中添加对my-app JAR的依赖
```xml
...
com.mycompany.app
my-app
1.0-SNAPSHOT
...
在子模块的pom.xml中分别添加一下元素
com.mycompany.app
app
1.0-SNAPSHOT
...
在最顶层的目录运行:
mvn verify
在my-webapp/target/my-webapp.war中生成了war文件,并且包含了JAR
$ jar tvf my-webapp/target/my-webapp-1.0-SNAPSHOT.war
0 Fri Jun 24 10:59:56 EST 2005 META-INF/
222 Fri Jun 24 10:59:54 EST 2005 META-INF/MANIFEST.MF
0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/
0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/
0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/
3239 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.xml
0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/
215 Fri Jun 24 10:59:56 EST 2005 WEB-INF/web.xml
123 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.properties
52 Fri Jun 24 10:59:56 EST 2005 index.jsp
0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/
2713 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/my-app-1.0-SNAPSHOT.jar