手记

耗时一周,万字总结Maven简明教程,与君共勉!

什么是Mavne

Maven 是一个项目管理工具它包含了一个项目对象模型 (POMProject Object Model)一组标准集合。由于 Maven 使用标准目录布局和默认构建生命周期开发团队几乎可以立即自动化项目的构建基础设施。在多个开发团队环境的情况下Maven 可以在很短的时间内按照标准设置工作方式。

Maven 之前更多的是使用 Ant 的项目构建工具Ant 有一个特点每次都得写每次都写的差不多配置也臃肿。所以后来搞出来 Maven。Maven 就是最先进的版本构建工具吗不是的只不过目前在 Java 领域 Maven 使用比较多。除了 Maven还有 Gradle。

它的主要功能有

  • 提供了一套标准化的项目结构
  • 提供了一套标准化的构建流程编译测试打包发布……
  • 提供了一套依赖管理机制。

为了实现上面的主要功能Maven提供了两大核心

  • 依赖管理对 jar 的统一管理(Maven 提供了一个 Maven 的中央仓库当我们在项目中添加完会自动去中央仓库下载相关的依赖并且解决依赖的依赖问题)
  • **项目构建**对项目进行编译、测试、打包、部署、上传到私服等

Maven模型

下面来谈一谈Maven模型的整体结构包括三个部分

  • 项目对象模型 (Project Object Model)
  • 依赖管理模型(Dependency)
  • 插件(Plugin)

如上图所示包括蓝、黄两个部分分别对应着依赖关系和项目构建两大核心功能。

首当其冲的一个核心就是项目对象模型也就是经常使用的pom.xml

另外一个就是项目构建Maven的项目构建可以按照生命周期具备以下三个标准生命周期

  • clean项目清理的处理
  • default(或 build)项目部署的处理
  • site项目站点文档创建的处理

项目对象模型POM

POM 代表项目对象模型。它是 Maven 中的基本工作单元。它是一个 XML 文件作为 pom.xml 驻留在项目的跟目录中。POM 不仅包含有关项目的信息以及 Maven 用于构建项目的各种配置详细信息 还包含目标和插件。

在执行任务或目标时Maven 在当前目录中查找 POM。它读取 POM获取所需的配置信息然后执行目标。在POM文件中常见的配置包括一下几点

  • 项目依赖
  • 插件
  • 目标
  • 建立档案
  • 项目版本
  • 开发商
  • 邮件列表

在创建 POM 之前我们应该首先确定项目(groupId)、项目名称(artifactId) 和版本因为这些属性有助于在存储库中唯一标识项目。

<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.coderV</groupId>
    <artifactId>coderV</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>com.coderV</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
            //...
            </plugin>
        </plugins>
    </build>
</project>

接下来仔细拆解POM文件结构

1、项目标识符

Maven 使用一组标识符也称为坐标来唯一标识项目并指定应如何打包项目工件

  • groupId 创建项目的公司或组的唯一基本名称
  • artifactId 项目的唯一名称
  • **version**项目的一个版本
  • packaging 一种打包方法例如WAR / JAR / ZIP

其中所有 POM 文件都需要项目元素和三个必填字段groupId、artifactId、version

其中的前三个 ( groupId:artifactId:version ) 结合形成唯一标识符并且是您指定项目将使用的外部库例如 JAR版本的机制。

详细说明一些基本的项目标识符6

参数名称 描述
Project root 这是项目root的标签。您需要指定基本架构设置例如 apache 架构和 w3.org 规范。
Model version 模型版本应为 4.0.0。
**groupId ** 这是项目组/公司的 ID。这在组织或项目中通常是唯一的。
artifactId 这是项目的 ID。这通常是项目的名称
version 这是项目的版本

2、依赖管理

Maven 使用存储库的概念进行依赖管理项目中使用的这些外部库称为依赖项。Maven 中的依赖项管理功能可确保从中央存储库自动下载这些库因此您不必将它们存储在本地。

这是 Maven 的一个关键特性并提供以下好处

  • 结合本地仓库大幅减少从远程存储库下载的数量减少存储使用量
  • 管理项目依赖性变得更容易
  • 提供了一个有效的平台用于在组织内外交换二进制组件而无需每次都从源代码手动安装组件

为了声明对外部库的依赖您需要提供库的groupId、artifactId、verison。让我们看一个例子

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.16</version>
</dependency>

当 Maven 处理依赖项时它会将 Spring Core 库下载到本地 Maven 存储库中并在项目中使用

传递依赖

如下图所示项目 A 依赖于项目 BB 又依赖于项目 C此时 B 是 A 的直接依赖C 是 A 的间接依赖

Maven 的依赖传递机制是指不管 Maven 项目存在多少间接依赖POM 中都只需要定义其直接依赖不必定义任何间接依赖Maven 会动读取当前项目各个直接依赖的 POM将那些必要的间接依赖以传递性依赖的形式引入到当前项目中。Maven 的依赖传递机制能够帮助用户一定程度上简化 POM 的配置。

基于 A、B、C 三者的依赖关系根据 Maven 的依赖传递机制我们只需要在项目 A 的 POM 中定义其直接依赖 B在项目 B 的 POM 中定义其直接依赖 CMaven 会解析 A 的直接依赖 B的 POM 将间接依赖 C 以传递性依赖的形式引入到项目 A 中。

通过这种依赖传递关系可以使依赖关系树迅速增长到一个很大的量级很有可能会出现依赖重复依赖冲突等情况Maven 针对这些情况提供了如下功能进行处理。

  • 依赖范围Dependency scope
  • 依赖调解Dependency mediation
  • 可选依赖Optional dependencies
  • 排除依赖Excluded dependencies
  • 依赖管理Dependency management

Maven 具有以下 6 中常见的依赖范围如下表所示。

依赖范围 描述
compile 编译依赖范围scope 元素的缺省值。使用此依赖范围的 Maven 依赖对于三种 classpath 均有效即该 Maven 依赖在上述三种 classpath 均会被引入。例如log4j 在编译、测试、运行过程都是必须的。
test 测试依赖范围。使用此依赖范围的 Maven 依赖只对测试 classpath 有效。例如Junit 依赖只有在测试阶段才需要。
provided 已提供依赖范围。使用此依赖范围的 Maven 依赖只对编译 classpath 和测试 classpath 有效。例如servlet-api 依赖对于编译、测试阶段而言是需要的但是运行阶段由于外部容器已经提供故不需要 Maven 重复引入该依赖。
runtime 运行时依赖范围。使用此依赖范围的 Maven 依赖只对测试 classpath、运行 classpath 有效。例如JDBC 驱动实现依赖其在编译时只需 JDK 提供的 JDBC 接口即可只有测试、运行阶段才需要实现了 JDBC 接口的驱动。
system 系统依赖范围其效果与 provided 的依赖范围一致。其用于添加非 Maven 仓库的本地依赖通过依赖元素 dependency 中的 systemPath 元素指定本地依赖的路径。鉴于使用其会导致项目的可移植性降低一般不推荐使用。
import 导入依赖范围该依赖范围只能与 dependencyManagement 元素配合使用其功能是将目标 pom.xml 文件中 dependencyManagement 的配置导入合并到当前 pom.xml 的 dependencyManagement 中。

依赖范围与三种 classpath 的关系一览表如下所示。

依赖范围 编译 classpath 测试 classpath 运行 classpath 例子
compile log4j
test - - junit
provided - servlet-api
runtime - - JDBC-driver
system - 非 Maven 仓库的本地依赖
依赖范围对传递依赖的影响

项目 A 依赖于项目 BB 又依赖于项目 C此时我们可以将 A 对于 B 的依赖称之为第一直接依赖B 对于 C 的依赖称之为第二直接依赖。

B 是 A 的直接依赖C 是 A 的间接依赖根据 Maven 的依赖传递机制间接依赖 C 会以传递性依赖的形式引入到 A 中但这种引入并不是无条件的它会受到依赖范围的影响。

传递性依赖的依赖范围受第一直接依赖和第二直接依赖的范围影响如下表所示。

compile test provided runtime
compile compile - - runtime
test test - - test
provided provided - provided provided
runtime runtime - - runtime

注上表中左边第一列表示第一直接依赖的依赖范围上边第一行表示第二直接依赖的依赖范围。交叉部分的单元格的取值为传递性依赖的依赖范围若交叉单元格取值为“-”则表示该传递性依赖不能被传递。

通过上表可以总结出以下规律(*)

  • 当第二直接依赖的范围是 compile 时传递性依赖的范围与第一直接依赖的范围一致
  • 当第二直接依赖的范围是 test 时传递性依赖不会被传递
  • 当第二直接依赖的范围是 provided 时只传递第一直接依赖的范围也为 provided 的依赖且传递性依赖的范围也为 provided
  • 当第二直接依赖的范围是 runtime 时传递性依赖的范围与第一直接依赖的范围一致但 compile 例外此时传递性依赖的范围为 runtime。
依赖调节

Maven 的依赖传递机制可以简化依赖的声明用户只需要关心项目的直接依赖而不必关心这些直接依赖会引入哪些间接依赖了。为了避免出现依赖重复的问题Maven 通过依赖调节来确定间接依赖的引入路径。

依赖调节遵循以下两条原则

  1. 引入路径短者优先
  2. 先声明者优先
引入路径短者优先

引入路径短者优先顾名思义当一个间接依赖存在多条引入路径时引入路径短的会被解析使用。

例如A 存在这样的依赖关系
A->B->C->D(1.0)
A->X->D(2.0)

D 是 A 的间接依赖但两条引入路径上有两个不同的版本很显然不能同时引入否则造成重复依赖的问题。根据 Maven 依赖调节的第一个原则引入路径短者优先D1.0的路径长度为 3D2.0的路径长度为 2因此间接依赖 D2.0将从 A->X->D(2.0) 路径引入到 A 中。

先声明者优先

先声明者优先顾名思义在引入路径长度相同的前提下POM 文件中依赖声明的顺序决定了间接依赖会不会被解析使用顺序靠前的优先使用。

例如A 存在以下依赖关系
A->B->D(1.0)
A->X->D(2.0)

D 是 A 的间接依赖其两条引入路径的长度都是 2此时 Maven 依赖调节的第一原则已经无法解决需要使用第二原则先声明者优先。

Maven排除依赖和可选依赖

我们知道 Maven 依赖具有传递性例如 A 依赖于 BB 依赖于 C在不考虑依赖范围等因素的情况下Maven 会根据依赖传递机制将间接依赖 C 引入到 A 中。但如果 A 出于某种原因希望将间接依赖 C 排除那该怎么办呢Maven 为用户提供了两种解决方式排除依赖Dependency Exclusions和可选依赖Optional Dependencies。

排除依赖

假设存在这样的依赖关系A 依赖于 BB 依赖于 XB 又依赖于 Y。B 实现了两个特性其中一个特性依赖于 X另一个特性依赖于 Y且两个特性是互斥的关系用户无法同时使用两个特性所以 A 需要排除 X此时就可以在 A 中将间接依赖 X 排除。

排除依赖是通过在 A 中使用 exclusions 元素实现的该元素下可以包含若干个 exclusion 子元素用于排除若干个间接依赖示例代码如下

    <dependencies>
        <dependency>
            <groupId>net.biancheng.www</groupId>
            <artifactId>B</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <!-- 设置排除 -->
                <!-- 排除依赖必须基于直接依赖中的间接依赖设置为可以依赖为 false -->
                <!-- 设置当前依赖中是否使用间接依赖 -->
                <exclusion>
                    <!--设置具体排除-->
                    <groupId>net.biancheng.www</groupId>
                    <artifactId>X</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

关于 exclusions 元素及排除依赖说明如下

  • 排除依赖是控制当前项目是否使用其直接依赖传递下来的间接依赖
  • exclusions 元素下可以包含若干个 exclusion 子元素用于排除若干个间接依赖
  • exclusion 元素用来设置具体排除的间接依赖该元素包含两个子元素groupId 和 artifactId用来确定需要排除的间接依赖的坐标信息
  • exclusion 元素中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖无需指定版本 version。
可选依赖

与上文的应用场景相同也是 A 希望排除间接依赖 X除了在 B 中设置可选依赖外我们还可以在 B 中将 X 设置为可选依赖。

设置可选依赖

在 B 的 POM 关于 X 的依赖声明中使用 optional 元素将其设置成可选依赖示例配置如下:

    <dependencies>
        <dependency>
            <groupId>net.biancheng.www</groupId>
            <artifactId>X</artifactId>
            <version>1.0-SNAPSHOT</version>
            <!--设置可选依赖  -->
            <optional>true</optional>
        </dependency>
    </dependencies>

关于 optional 元素及可选依赖说明如下

  • 可选依赖用来控制当前依赖是否向下传递成为间接依赖
  • optional 默认值为 false表示可以向下传递称为间接依赖
  • 若 optional 元素取值为 true则表示当前依赖不能向下传递成为间接依赖。
排除依赖 VS 可选依赖

排除依赖和可选依赖都能在项目中将间接依赖排除在外但两者实现机制却完全不一样。

    • 排除依赖是控制当前项目是否使用其直接依赖传递下来的接间依赖
    • 可选依赖是控制当前项目的依赖是否向下传递
    • 可选依赖的优先级高于排除依赖
    • 若对于同一个间接依赖同时使用排除依赖和可选依赖进行设置那么可选依赖的取值必须为 false否则排除依赖无法生效。

3、Maven 仓库

在 Maven 术语中仓库是存储所有项目的 jar包的地方Maven 可以轻松使用它们。根据类别可以将Maven仓库分为三类

1本地仓库
默认情况下每个本地计算机的用户目录下都有一个路径名为.m2/repository/的仓库目录这个就是本地的仓库

也可以在 settings.xml 文件配置本地仓库的路径

2远程仓库
远程仓库也称为私服由公司或者项目组维护是开发人员自己定义的远程仓库其中包含项目所需要的库或其他的jar包可以通过标签来制定远程仓库地址

   <repositories>
      <repository>
         <id>companyname.lib1</id>
         <url>http://download.companyname.org/maven2/lib1</url>
      </repository>
   </repositories>

3中央仓库

Maven中央仓库是Maven社区提供的仓库。它包含大量常用的库。当 Maven 在本地存储库中找不到任何依赖项时就会从中央仓库中搜索

其中中央仓库有以下几点需要注意

  • 此存储库由 Maven 社区管理。
  • 不需要配置但可以替换中央仓库的源为制定的镜像源
  • 它需要互联网访问才能搜索。
Maven 依赖加载顺序

当我们执行 Maven 构建命令时Maven 开始按以下顺序查找依赖库

  • 第 1 步- 在本地存储库中搜索依赖项如果未找到则转到第 2 步否则执行进一步处理。
  • 第 2 步- 如果没有提到远程存储库则跳转到第 3 步。在一个或多个远程存储库中搜索依赖项如果找到则将其下载到本地存储库以供将来参考
  • 第 3 步- 在中央存储库中搜索依赖项将其下载到本地存储库以供将来参考。如果未找到 Maven 只是停止处理并抛出错误无法找到依赖项

4、属性(properties)

自定义属性有助于pom.xml文件更易于阅读和维护。一个十分经典的使用场景就是通过自定义属性来定义项目依赖项的版本。

Mavenproperties是一个占位符通过properties定义不同属性名的值

例如下面的例子中通过<properties>定义了一个spring.version的属性其具体的值为 5.3.16如果想要将 Spring 升级到更新的版本就只需更改<spring.version>属性标签内的值所有在其<version>标签中使用该属性的依赖项都将更新。

<properties>
    <spring.version>5.3.16</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

另一个常用的场景就是使用<properties>定义构建路径的变量例如

<properties>
    <project.build.folder>${project.build.directory}/tmp/</project.build.folder>
</properties>

<plugin>
    //...
    <outputDirectory>${project.resources.build.folder}</outputDirectory>
    //...
</plugin>

5、Build

build部分也是 Maven POM 中非常重要的部分。它提供有关默认 Maven目标、已编译项目的目录和应用程序的最终名称的信息。默认build如下所示

<build>
  	<!--当项目没有规定目标Maven2叫做阶段phase时的默认值 -->
		<!--必须跟命令行上的参数相同例如jar:jar或者与某个阶段phase相同例如install、compile等 -->
    <defaultGoal>install</defaultGoal>
  	<!-- 构建产生的所有文件存放的目录,默认为${basedir}/target即项目根目录下的target -->
    <directory>${basedir}/target</directory>
  	<!-- 产生的构件的文件名默认值是${artifactId}-${version}-->
    <finalName>${artifactId}-${version}</finalName>
 	  <!--当filtering开关打开时使用到的过滤器属性文件列表。 -->
		<!--项目配置信息中诸如${spring.version}之类的占位符会被属性文件中的实际值替换掉 -->
    <filters>
      <filter>filters/filter1.properties</filter>
    </filters>
    //...
</build>

编译工件的默认输出文件夹名为*target*打包工件的最终名称由*artifactId*和*version*组成但您可以随时更改。

6、配置文件

构建配置文件是一组配置值可用于设置或覆盖 Maven 构建的默认值。使用构建配置文件您可以为不同的环境例如生产环境和开发环境自定义构建。

配置文件主要分为两种类型一种是在定义在项目上pom.xml文件中另一种是定义在setting.xml上

<profiles>
    <profile>
        <id>production</id>
        <build>
            <plugins>
                <plugin>
                //...
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>development</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <build>
            <plugins>
                <plugin>
                //...
                </plugin>
            </plugins>
        </build>
     </profile>
 </profiles>

定义在pom.xml文件上的profile可以看做pom.xml的副本拥有与pom.xml相同的子元素与配置方法。

正如您在上面的示例中看到的默认配置文件设置为development。如果要运行生产配置文件可以使用以下 Maven 的 -P 命令显示的激活一个profile

mvn clean install -P production

Maven 构建生命周期

每个 Maven 构建都遵循指定的生命周期。您可以执行多个构建生命周期目标包括编译项目代码、创建包以及在本地 Maven 依赖项存储库中安装存档文件的目标。

以下列表显示了最重要的 Maven生命周期阶段

  • validate: 检查项目的正确性
  • compile: 将提供的源代码编译成二进制工件
  • test: 执行单元测试
  • package: 将编译后的代码打包*到归档文件中
  • integration-test: 执行额外的测试这需要打包
  • verify——检查包是否有效
  • install – 将包文件安装到本地 Maven 存储库
  • deploy – 将包文件部署到远程服务器或存储库

1、Clean 生命周期

当我们执行 mvn post-clean 命令时Maven 调用 clean 生命周期它包含以下阶段

  • pre-clean执行一些需要在clean之前完成的工作
  • clean移除所有上一次构建生成的文件
  • post-clean执行一些需要在clean之后立刻完成的工作

在一个生命周期中运行某个阶段的时候它之前的所有阶段都会被运行也就是说如果执行 mvn clean 将运行pre-clean, clean两个生命周期阶段运行 mvn post-clean 则运行pre-clean, clean, post-clean三个生命周期阶段

2、Default (Build) 生命周期

这是 Maven 的主要生命周期被用于构建应用包括下面的 23 个阶段

生命周期阶段 描述
validate校验 校验项目是否正确并且所有必要的信息可以完成项目的构建过程。
initialize初始化 初始化构建状态比如设置属性值。
generate-sources生成源代码 生成包含在编译阶段中的任何源代码。
process-sources处理源代码 处理源代码比如说过滤任意值。
generate-resources生成资源文件 生成将会包含在项目包中的资源文件。
process-resources 处理资源文件 复制和处理资源到目标目录为打包阶段最好准备。
compile编译 编译项目的源代码。
process-classes处理类文件 处理编译生成的文件比如说对Java class文件做字节码改善优化。
generate-test-sources生成测试源代码 生成包含在编译阶段中的任何测试源代码。
process-test-sources处理测试源代码 处理测试源代码比如说过滤任意值。
generate-test-resources生成测试资源文件 为测试创建资源文件。
process-test-resources处理测试资源文件 复制和处理测试资源到目标目录。
test-compile编译测试源码 编译测试源代码到测试目标目录.
process-test-classes处理测试类文件 处理测试源码编译生成的文件。
test测试 使用合适的单元测试框架运行测试Juint是其中之一。
prepare-package准备打包 在实际打包之前执行任何的必要的操作为打包做准备。
package打包 将编译后的代码打包成可分发格式的文件比如JAR、WAR或者EAR文件。
pre-integration-test集成测试前 在执行集成测试前进行必要的动作。比如说搭建需要的环境。
integration-test集成测试 处理和部署项目到可以运行集成测试环境中。
post-integration-test集成测试后 在执行集成测试完成后进行必要的动作。比如说清理集成测试环境。
verify 验证 运行任意的检查来验证项目包有效且达到质量标准。
install安装 安装项目包到本地仓库这样项目包可以用作其他本地项目的依赖。
deploy部署 将最终的项目包复制到远程仓库中与其他开发者和项目共享。

Site 生命周期

Maven Site 插件一般用来创建新的报告文档、部署站点等。

  • pre-site执行一些需要在生成站点文档之前完成的工作
  • site生成项目的站点文档
  • post-site 执行一些需要在生成站点文档之后完成的工作并且为部署做准备
  • site-deploy将生成的站点文档部署到特定的服务器上

插件Plugin

插件管理与依赖管理原理一样、不同的是定义的元素标签不一样、插件管理标签是build标签的子标签pluginManagement

pluginManagement 用来做插件管理的。它是表示插件声明即你在项目中的pluginManagement下声明了插件Maven不会加载该插件pluginManagement声明可以被继承。如下面的例子

<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.1</version>
            <configuration>
                <attach>true</attach>
            </configuration>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</pluginManagement>

<!-- 子POM-->
<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
    </plugin>
</plugins>

原创文章总结不易禁止侵权转载。转载请先私信知乎「编程大K」征求同意

0人推荐
随时随地看视频
慕课网APP

热门评论

666

666


查看全部评论