07_project_01_maven

第六十三章 Apache Maven

1 简介

Apache Maven (有时简称为Maven)是Apache软件基金会开发的一个开源的、用于自动构建项目的工具。该工具可用于自动构建任何编程语言编写的工程。但是,由于一些历史原因,也或许因为Maven是由Java语言开发的,所以,Maven被主要用于构建Java工程。

Maven系统建立于工程对象模型(Project Object Model, or POM)的概念之上;它能够自动构建项目,自动生成项目报告,和自动生成项目文档等功能。Maven还能自动解析第三方库之间的依赖关系;自动地从Maven库(Maven Repository)获取第三方库(3rd-Party Libraries);完成项目构建。因此,使用Maven能极大的减少开发人员和维护人员的工作量。

因为Maven本身是用Java语言开发的,所以需要安装Java运行环境(Java Runtime Environment, or JRE)才能运行Maven工具。

2 项目对象模型(Project Object Model)

每个Maven项目的所有内容都是在一个pom.xml文件里描述的。该文件使用XML格式语言描述与该项目相关的信息(包括项目名称、项目描述、项目版本等)。一般的,pom.xml文件放在工程的根目录下,该文件包含一个根节点<project>来表示该项目。

2.1 项目结构

由Maven管理的Java项目的结构如下。在项目的根目录下有pom.xml项目管理文件,和src和target两个目录。src目录下存放的是源代码文件;target目录下存放的是项目构建的输出文件(例如:jar文件)。target目录可由Maven自动创建。

在src目录下有main和test两个目录。main目录存放的是功能性的源代码文件;而test目录下存放的是项目的测试源代码文件。这些文件用于测试src/main目录下的源代码。

在main和test目录下分别有java和resources两个目录。其中,java目录存放的是项目的源代码文件(.java文件);resources目录存放的是项目的配置文件(例如:日志配置文件,或者项目的配置文件)。在打包过程中,resources目录下的文件会自动打包在.jar或者.war文件中。在运行时,程序也能找到并且读取resources目录下的文件。

当运行Maven构建该项目时,Maven会读取pom.xml文件中的内容,并按照其内容,依次完成目标(Goals)。

A-Maven-Project
 |- pom.xml
 |- src
 |  |- main
 |  |  |- java
 |  |  |- resources
 |  |- test
 |  |  |- java
 |  |  |- resources
 |- target

2.2 pom.xml的例子

下面是一个简单的pom.xml的例子。在该pom.xml中,modelVersion标签指明了该POM模型的版本是4.0.0。目前,4.0.0是Maven支持的唯一的版本。所以,每一个pom.xml都需要使用4.0.0版本。

groupId标签指明了组织名称或者项目名称。这个名称不应与其他组织或者项目名称相同。一个好的做法是使用点符号(.)的方式唯一指定一个项目名称,就像我们在Java语言中使用包名称(Package Name)那样,防止命名冲突。但是,groupId和包名称是完全不同的。在下面的例子中,我们指定了groupId为com.littlewaterdrop;说明这个项目是小水滴的一个项目。

artifactId标签指明了该项目的名称。这个名称不能与其他项目的名称相同。例如,在下面的例子中,我们指定了项目名称为maven-example。

version标签指明了该项目的版本号。因此,groupId:artifactId:version可以唯一确定一个组织下的一个项目的一个版本。

packaging标签指明了项目打包的目标文件的格式。在Maven项目中,最常用的两个打包文件格式是jar和war。当packaging未被定义时,缺省的目标格式是jar。Maven支持的其他格式还有:pom、maven-plugin、ejb、ear和rar。

dependencies标签定义了该项目所依赖的第三方库。当在pom.xml文件中描述了这些依赖库之后,Maven会自动地在Maven库(Maven Repository)中查找并下载这些第三方库,并自动完成项目的构建。项目之间的依赖关系(Dependency)属于Maven支持的POM关系中的一种。我们将在下面详细讲解Maven支持的POM关系(POM Relationships)。

<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.littlewaterdrop</groupId>
	<artifactId>maven-example</artifactId>
	<version>1.0</version>
	<packaging>jar</packaging>
	<dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
	</dependencies>
</project>

3 POM关系(POM Relationships)

Maven最强的功能是处理项目之间的关系。项目关系包括:项目依赖关系(Dependency)、项目继承关系(Inheritance)、和项目聚合关系(Aggregation)。

3.1 项目依赖关系(Project Dependency)

项目依赖关系是指某一个项目的正确构建和运行依赖于另一个项目,那么,我们认为前者项目依赖于后者项目。pom.xml维护着一个项目依赖列表。当这个列表中的所有依赖关系可用时,当前项目才能正确构建和运行。Maven不仅能处理项目之间的直接关系,还能处理间接依赖关系。换句话说,Maven维护着一张项目依赖图。在构建时,Maven能自动获取所有的直接地/间接地依赖于当前项目的第三方库。因此,开发人员只需要关注于项目的直接依赖关系,Maven会自动处理所有的间接依赖。

在下面的例子中,pom.xml的依赖列表包含了一个依赖库junit,它由groupId:artifictId:version来唯一标识junit的版本。

标签scope标识了在哪个构建阶段会使用该依赖库。常见的scope的值为:compile、provided、runtime、test和system。scope标签的默认值为compile。其中,从名字可以看出compile、runtime、和test分别表示的是编译、运行、和测试三个阶段。provided表示在编译和运行时都需要这个依赖库。system与provided类似,不同之处在于这个依赖库由开发者提供;Maven不会从Maven的库中查找。

标签type标识了依赖库的类型。默认的类型是jar。

标签optional标识该依赖是否是可选的。

<project>
	...
	<dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.0-M1</version>
            <scope>test</scope>
			<type>jar</type>
			<optional>true</optional>
        </dependency>
	</dependencies>
	...
</project>

在提供便利的同时,Maven的自动解析项目依赖的过程也会出现问题。一个常见的问题是项目依赖库的版本可能发生冲突。例如:项目A依赖于项目B和C。项目B依赖于项目D的1.0版本;而项目C依赖于项目D的2.0版本。那么,在构建项目A时,Maven会使用项目D的1.0版本还是2.0版本呢?

A --> B -> D:1.0
  \ 
   -> C -> D:2.0

为了解决这个依赖版本冲突的问题,Maven采用了软依赖(Soft Dependency)硬依赖(Hard Dependency)的概念。软依赖是指当发生冲突时,或者指定版本不可用时,Maven可以使用其他版本来代替。硬依赖是指Maven只能使用指定的版本号。当软依赖和硬依赖发生冲突时,Maven会尽力满足硬依赖。但是,如果当两个硬依赖发生冲突,并且不存在解决方案时,Maven会报错,构建失败。

软/硬依赖是与版本号一起指定的。例如:1.0 是一个软依赖;[1.0]是一个硬依赖,表示只能用版本1.0;(, 1.0]是一个硬依赖,表示可以使用1.0版本或者早于1.0的版本;[1.2, 1.3]是一个硬依赖,表示只能使用1.2和1.3之间的版本(包含1.2和1.3版本)。

使用依赖关系,pom.xml文件还可以指明不能使用某个依赖库。被排除的依赖库可以放在<exclusions></exclusions>的标签之间。

...
<dependencies>
  <dependency>
    <groupId>com.littlewaterdrop</groupId>
    <artifactId>maven-dependency</artifactId>
    <version>1.0</version>
    <exclusions>
      <exclusion>
        <groupId>com.littlewaterdrop</groupId>
        <artifactId>other-dependency</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
</dependencies>
...

点击此处下载Maven项目模板。该项目模板使用了项目依赖关系构建项目。

3.2 项目继承关系(Project Inheritance)

Maven中的项目继承关系与面向对象程序设计的类继承关系非常相似。在项目中,开发人员可以定义一个父pom.xml文件,然后再使用子pom.xml文件,继承父pom.xml中的内容。因此,在子pom.xml文件中可以直接使用在父pom.xml中定义的属性。当父pom.xml中的内容不再适用时,开发人员还可以在子pom.xml文件中覆盖父pom.xml定义的属性。在POM继承关系下,父pom.xml被称为超级POM(Super POM)。使用继承关系能有效的减少pom.xml文件中的冗余,并且在多个项目之间共享信息。

例如,在下面的父pom.xml定义了groupId、artifactId、version和packaging。在父pom.xml中,packaging标签的值必须是pom。

在子pom.xml中可以继承父pom.xml中的内容,但是,Maven规定:artifactIdnameprerequisites是不会从父pom.xml中继承的。

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.littlewaterdrop</groupId>
  <artifactId>parent-artifact-id</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
</project>

在子pom.xml中可以使用<parent></parent>来引用父pom.xml。在parent标签中的groupId、artifactId和version要和父pom.xml中定义的值相同。在下面的子pom.xml中,它定义了该项目使用的artifactId,所以,该项目的唯一标识为com.littlewaterdrop:child-artifact-id:1.0。

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.littlewaterdrop</groupId>
    <artifactId>parent-artifact-id</artifactId>
    <version>1.0</version>
  </parent>
 
  <artifactId>child-artifact-id</artifactId>
</project>

点击此处下载使用父/子POM的项目模板。

3.3 聚合/多模块关系(Aggregation or Multi-Module)

有时,一个项目是由多个模块组成的,构建项目就是构建这些模块。因此,开发人员可以使用聚合/多模块关系来组织项目。一个典型的多模块项目的文件结构如下。在项目的根目录会有一个pom.xml,用于表示该项目包含的模块名称。在根目录下还有Module-A和Module-B两个目录,分别用于表示该项目中包含的两个模块。Module-A和Module-B目录中各自维护着一份独立的项目结构,也有着各自独立的pom.xml文件。

A-Maven-Multi-Module-Project
 |- pom.xml
 |- Module-A
 |  |- pom.xml
 |  |- src
 |  |  |- main
 |  |  |  |- java
 |  |  |  |- resources
 |  |  |- test
 |  |  |  |- java
 |  |  |  |- resources
 |- Module-B
 |  |- pom.xml
 |  |- src
 |  |  |- main
 |  |  |  |- java
 |  |  |  |- resources
 |  |  |- test
 |  |  |  |- java
 |  |  |  |- resources

下面是一个多模块项目的主pom.xml文件(放在项目根目录下的pom.xml文件)。父pom.xml文件的packaging的值必须为pom。该文件使用<modules></modules>包含项目中的各个模块。Maven在构建多模块项目时,Maven会解析这些模块之间的依赖关系,并按照其依赖关系依次构建整个项目。所以,开发人员可以按照任意顺序组织和包含模块。

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.littlewaterdrop</groupId>
  <artifactId>parent-artifact-id</artifactId>
  <version>2.0</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>module-A</module>
    <module>module-B</module>
  </modules>
</project>

在module-A或者module-B模块中的pom.xml文件可以使用<parent></parent>来引用和继承父pom.xml的内容。和前一小节讲述了父/子pom.xml用法类似。

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.littlewaterdrop</groupId>
    <artifactId>parent-artifact-id</artifactId>
    <version>1.0</version>
  </parent>
 
  <artifactId>child-artifact-id</artifactId>
</project>

点击此处下载多模块的项目模板。

4 POM属性(Properties)

POM属性是pom.xml文件中的“变量”。在定义了属性之后,开发人员可以在pom.xml文件的任何地方使用属性。例如:开发人员在pom.xml文件中定义了变量VAR,则可以使用${VAR}来引用变量的值。

例如:在下面的pom.xml中,我们定义了名为commons.logging.version的属性。所有的属性定义需要放在<properties></properties>之间,标签的名称就是该属性的名称。在依赖区(dependencies标签)中,${commons.logging.version}表示的是属性commons.logging.version的值,即1.2。

<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.littlewaterdrop</groupId>
	<artifactId>maven-example</artifactId>
	<version>1.0</version>
	<packaging>jar</packaging>

  <properties>
    <commons.logging.version>1.2</commons.logging.version>
  </properties>

	<dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>${commons.logging.version}</version>
        </dependency>
	</dependencies>
</project>

5 构造过程

Maven按照一个标准化的流程执行构造过程。这个标准化的构造过程被称为Maven构造生命周期(Build Lifecycles)

5.1 Maven构造生命周期(Build Lifecycles)和阶段(Phases)

Maven支持三个构建生命周期:default生命周期处理项目部署;clean生命周期清理项目多余的文件;site生命周期创建项目的文档。

Maven的构造生命周期是由阶段(Phase)构成的。default构建生命周期包含23个阶段;clean构建生命周期包含3个阶段;site构建生命周期包含4个阶段。在构建项目时,Maven会严格按照顺序执行各个阶段。

在每个阶段中,Maven使用目标(Goals)来表达为了达到某个目标需要执行的任务。目标是Maven可执行的最小单位,且必须与阶段(Phase)关联。Maven会按照阶段的顺序执行目标

5.2 Default构造生命周期

Default构造生命周期包含的阶段较多,她们依次是:

  1. validate:检验阶段,用于检验项目中的各项信息是否正确。
  2. initialize:初始化阶段,用于初始化构建流程中的状态和信息。
  3. generate-sources:代码生成阶段,用于生成源代码,用于编译阶段。
  4. process-sources:代码处理阶段,例如:过滤或者修改源代码中的一些值。
  5. generate-resources:资源生成阶段,例如:生成资源文件,以包含在包中。
  6. process-resources:资源处理阶段,例如:将资源拷贝到目标路径中。
  7. compile:编译阶段,用于编译项目源代码。
  8. process-classes:处理class文件阶段。在这个阶段中,可以处理Java的二进制代码(.class文件)。
  9. generate-test-sources:生成测试源代码阶段,可用于生成测试的源代码。
  10. process-test-sources:处理测试源代码阶段。例如:可以过滤或者修改测试源代码中的一些值。
  11. generate-test-resources:生成测试资源阶段,可用于生成测试使用的资源文件。
  12. process-test-resources:处理测试资源阶段,例如:可将资源拷贝到目标路径。
  13. test-compile:编译测试源代码阶段。
  14. process-test-classes:处理测试class文件阶段。在这个阶段可以处理测试用的Java二进制代码(.class文件)。
  15. test:测试阶段,运行测试程序。
  16. prepare-package:预打包阶段,为执行打包阶段做准备。
  17. package:打包阶段。将编译后的.class文件和资源文件打包成目标文件(例如:jar文件)。
  18. pre-integration-test:预集成测试阶段。为执行集成测试做准备。
  19. integration-test:集成测试阶段,运行集成测试。
  20. post-integration-test:后集成测试阶段。在这个阶段可以清理一些运行集成测试生成的临时文件。
  21. verify:校验阶段,用于校验生成的打包文件是否合格。
  22. install:安装目标文件,将目标文件推送至本地库,或者用于其他项目的依赖库。
  23. deploy:部署目标文件,将目标文件发送至远程库。

开发人员可以使用如下命令执行default构造生命周期。

mvn

5.3 clean构造生命周期

clean构造生命周期包含:

  1. pre-clean:预清理阶段,用于执行一些需要在清理阶段之前执行的任务。
  2. clean:清理阶段,用于清除前一次构建生成的文件。
  3. post-clean:后清理阶段,用于执行一些需要在清理阶段之后执行的任务。

开发人员可以使用如下命令执行Clean构造生命周期。

mvn clean

5.4 site构造生命周期

site构造生命周期包含:

  1. pre-site:site阶段的预处理阶段,运行于site阶段之前,为运行site阶段做准备。
  2. site:site阶段,用于生成项目文档。
  3. post-site:site阶段的后处理阶段,用于清理一些site阶段生成的临时文件。
  4. site-deploy:部署site阶段生成的项目文档。

开发人员可以使用如下命令执行site构造生命周期。

mvn site

5.5 Maven目标(Goals)

目标(Goals)是构建生命周期的最小单位,一个目标代表着一项具体的任务。例如:拷贝文件、编译源代码、打包等都是一个个具体的目标。Maven提供了一些随Maven发布的目标;开发人员也可以根据自身需求开发新的目标。

每个目标都绑定于一个阶段之中。换句话说,目标只能在所绑定的阶段中执行。Maven提供的目标有:

构建生命周期(Lifecycle)阶段(phase)插件:目标(plugin:goal)
defaultprocess-resourcesresources:resources
defaultcompilecompiler:compile
defaultprocess-test-resourcesresources:testResources
defaulttest-compilecompiler:testCompile
defaulttestsurefire:test
defaultpackagejar:jar, war:war
defaultinstallinstall:install
defaultdeploydeploy:deploy
cleancleanclean:clean
sitesitesite:site
sitesite-deploysite:deploy

开发人员可以单独运行一个目标。例如:开发人员可以使用如下命令编译项目。

mvn compile:compile

5.6 Maven插件(Plugin)

Maven插件(Maven Plugins)是一组目标的集合。一个插件可以包含运行在不同阶段的目标。

例如,在如下的插件中定义了两个目标integration-test和verify。她们分别被绑定在integration-test阶段和verify阶段,用于运行集成测试和检查集成测试结果。

<build>
  <plugins>
    <plugin>
      <artifactId>maven-failsafe-plugin</artifactId>
      <version>${maven.failsafe.version}</version>
      <executions>
        <execution>
          <goals>
            <goal>integration-test</goal>
            <goal>verify</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

开发人员可以使用如下命令单独运行一个目标(Goal),而不需要运行整个阶段(Phase)。

mvn PLUGIN:GOAL

6 Maven库 (Maven Repository)

Maven之所以能自动查找并获取第三方代码库(3rd-Party Libraries),是因为Maven维护着一个庞大的Maven库(Maven Repository)。在这个库中,Maven存放了大量的第三方代码库。这些第三方代码库在Maven库中发布他们的.jar文件、插件(plugins)、或者其他的项目发布的文件(Project Artifact)。

Maven库分为三个种类,它们分别是本地库(Local Repository)远程库(Remote Repository),和中心库(Central Repository)

本地库是在本地服务器上的一个目录,它保存着项目所依赖的所有文件。当第一次运行Maven时,Maven会自动地从其他库中查找依赖的文件,并下载到本地库中。所以,当Maven再次运行时,Maven能够直接从本地库中读取依赖文件,而无需再从远程库或者中心库读取。

远程库是由开发人员维护的依赖库。在远程库中,开发人员可以放置一些公司或者组织内部的项目文件,或者经特殊处理过的项目文件。这个远程库仅提供内部开发人员使用。

中心库是由Maven社区提供和维护的项目文件库。它包含了大量的,开发人员经常使用的第三方代码库。Maven中心库里的所有项目都可以在Maven Central Repository Search查找。

当Maven运行时,查找项目依赖库的顺序为:

  1. Maven首先在本地库中查找第三方依赖库。
  2. 如果在本地库中未找到,则在中心库中查找。
  3. 如果在中心库中也没查找到,且该项目未配置远程库的话,Maven构建失败。
  4. 如果该项目配置了远程库,则Maven还会在远程库中查找依赖库。如果查找失败,则Maven构建失败。

7 创建新的Maven项目

开发人员可以按照上述Maven项目结构和POM的工作原理来手动创建一个Maven项目。本文还提供了三个Maven项目的模板,方便读者快速创建项目(她们只包含了文件结构和空的源代码文件)。Maven项目模板Maven父/子继承关系模板Maven多模块项目模板

与此同时,Maven工具也提供了创建新项目的工具。开发人员可以按照如下的命令,并填入相应的groupId、artifactId即可生成一个新项目。

mvn archetype:generate \
-DgroupId=com.littlewaterdrop \
-DartifactId=maven-example \
-DinteractiveMode=false

在新项目创建完成后,可以运行下面的命令构建该项目。

cd maven-example
mvn clean package

8 结语

本章介绍了Maven工具的基本概念和项目的结构。POM是Maven工具的核心;Maven支持三种项目关系:依赖、继承、和多模块。根据这三种关系,Maven能自动的处理项目之间的依赖,帮助开发人员简化项目构建过程。

 

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.