Maven使用手册

@[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中支持在资源文件变量引用的语法${},这个属性可以定义在pom.xml,setting.xml,扩展的属性文件以及环境变量
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
  

标签定义为生成war,使用以下名称构建工程,会构建出arget/my-webapp.war
mvn package

# 如何同时构建多个工程
Maven可以处理多模块,首先根据之前构建的两个工程的父目录中创建pom.xml,目录结构如下:
+- pom.xml +- my-app | +- pom.xml | +- src | +- main | +- java +- my-webapp | +- pom.xml | +- src | +- main | +- webapp

pom.xml的内容为:
```xml

4.0.0

com.mycompany.app
app
1.0-SNAPSHOT
pom


my-app
my-webapp


在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
请使用浏览器的分享功能分享到微信等