Maven 开发者入门

前言

Maven自动化构建工具(build automation tool),它提供了以下功能:

  • 强大的依赖管理
  • 完善的生命周期管理(lifecycle & phase)
  • 以插件为核心(plugin & goal)
  • 支持多种打包方式
  • 支持子工程(模块)
  • 可重现构建(Reproducible builds)

Maven 广泛用于 Java 项目的自动化构建,可以跟其他工具(如 Jenkins)一起实现 CI & CD。

注:本文不会介绍 Maven 的下载、安装、配置,只介绍 Maven 在项目中如何使用。

目录结构

如果我们打算使用 Maven 构建我们的 Java 项目,我们 Java 项目的目录结构必须如下:

在这里插入图片描述

在这里插入图片描述

详细说明:

目录用途
project home项目的根目录,包含了 pom.xml 文件、其他子目录
src/main/java包含 Java 源代码
src/main/resources包含资源文件
src/test/java包含测试用的 Java 源代码
src/test/resources包含测试用的资源文件
target包含最终构建出来的产物,比如产生的包、字节码文件、资源文件、pom.xml

当然还可以包含其他文件,比如我们可以把项目的说明文件 README.md、数据库初始化语句 init.sql 放在 project home 下。

Maven 构建 Java 项目的过程:

  1. 读取 src/main/java 目录,获取 Java 代码编译;
  2. 读取 src/main/resources 目录,获取资源文件;
  3. 最后根据 pom.xml 配置的打包方式打包,把包放在 target目录下,并推送到 本地仓库 & 远程仓库。

注:以上只是 Maven 大概的构建过程,后文讲述细节。

POM

POM : Project Object Model,这是 Maven 用来表示整个项目信息的一个模型,用 xml表示,存放在项目根目录下的 pom.xml

pom.xml 是 Maven 的配置文件,项目的所有配置信息都放在这里。
当 Maven 构建项目时,会找到项目根目录下的 pom.xml,读取其中的配置信息,完成整个构建过程。

pom.xml 配置了以下信息:

  • 项目的 name, description, owner, developer, groupId, artifactId, version
  • 打包方式(pom, jar, war, ear
  • 依赖 (dependency
  • 插件(plugin
  • lifecycle
  • build directory - target(默认值)
  • source directory - src/main/java(默认值)
  • test source directory - src/test./java(默认值)

如果项目比较复杂,我们可以把整个项目拆分成 子工程(也叫 模块);
这样,父工程有一个 pom.xml(打包方式为 pom),每个子工程也有一个 pom.xml

详情参看:https://maven.apache.org/guides/introduction/introduction-to-the-pom.html

打包方式 - packaging

pom.xml 定义打包方式:

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
  <packaging>pom</packaging>  <!-- 定义打包方式 -->

</project>

打包方式:

方式示例说明
jar<packaging>jar</packaging>如果没有指定打包方式,默认为 jar
pom<packaging>pom</packaging>打包得到的项目只有 pom.xml 文件(一般在父子项目中,父项目的打包方式为 pom
war<packaging>war</packaging>可以部署到服务器运行
自定义的打包方式<packaging>自定义</packaging>可以自定义打包方式

依赖机制

依赖管理是 Maven 核心功能。那么,Maven 是怎么管理依赖呢?(比如,同一个依赖,引入了不同版本,最终使用哪个版本?)

Maven 依赖机制实现了以下功能:

  • Dependency mediation:依赖仲裁
  • Dependency management:依赖管理
  • Dependency scope:依赖范围
  • Excluded dependencies:排除依赖
  • Optional dependencies:可选依赖

Maven 依赖机制,详情参看:https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html。

直接依赖 & 传递依赖

  A
  └── B
      └── C
          └── D 2.0

A 是我们的项目, 其中 BA 的直接依赖,CDA 的传递依赖(transitive dependency)。

Dependency mediation - 依赖仲裁

  A
  ├── B
  │   └── C
  │       └── D 2.0
  └── E
      └── D 1.0

如上图所示,项目 A 的依赖关系形成了一棵树;
这棵树有两个分支,分别依赖了不同版本的 D:

  1. A -> B -> C -> D 2.0
  2. A -> E -> D 1.0

Maven 根据 neareast definition 确定使用哪个依赖,这个过程就叫 依赖仲裁

neareast definition - 最近定义

类似距离根节点的最短路径。

  1. 如果在依赖树中,同一个依赖出现了不同的版本,选择 距离根节点最近的那个依赖 的版本。
  2. 如果距离相同,选择 先出现的依赖 的版本。

这里 D 1.0 距离 A 最近,所以使用 D 1.0 版本。

Dependency management - 依赖管理

声明在 <dependency></dependency> 中的依赖,就是直接依赖,优先级高于传递依赖。

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

Dependency scope - 依赖范围

依赖可以声明自己的范围,表示项目在这个范围内才可以使用该依赖。
this allows you to only include dependencies appropriate for the current stage of the build.

依赖范围有 6 种:

  • compile: 默认范围,该依赖可以在任何阶段使用(测试、编译、运行),比如 Spring Core
  • test: 表示该依赖只在测试时可用,比如 JUnit
  • runtime: 表示该依赖只在运行时可用,比如 jdbc driver
  • provided: 表示该依赖应该由容器在运行时提供,比如 servlet-api
  • system: 跟 provided 类似,但是必须自己提供 jar 包,不是从远程仓库下载
  • imported: 用于从指定 POM <dependencyManagement> 导入依赖

Excluded dependencies - 排除依赖

排除某个传递依赖。

举例:
Y 依赖 Z
X 依赖 Y 时, X 可以排除 Y 的依赖 Z,这样 X 就不会依赖 Z 了。

使用场景:

  • 解决循环依赖
  • 不需要某个传递依赖
  • 获取不到某个传递依赖
<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
      <version>1.0</version>
      <exclusions>  <!-- 排除依赖 -->
        <exclusion>
          <groupId>group-c</groupId>
          <artifactId>excluded-artifact</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>
</project>

Optional dependencies - 可选依赖

类似 排除依赖。

举例:
Y 依赖 ZYZ 声明为 可选依赖;
X 依赖 Y 时,X 仅仅依赖 Y,不会依赖 Z(当然,X 可以声明直接依赖 Z)。

<project>
  ...
  <dependencies>
    <!-- declare the dependency to be set as optional(声明这个依赖为可选依赖)-->
    <dependency>
      <groupId>sample.ProjectA</groupId>
      <artifactId>Project-A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
      <optional>true</optional> <!-- value will be true or false only -->
    </dependency>
  </dependencies>
</project>

依赖的优先级

依赖的优先级关系:

  • 子工程 pom.xml 出现的依赖 > 父工程 pom.xml 出现的依赖
  • dependency management > dependency mediation(直接依赖优先级 > 依赖仲裁)
  • neareast definition 确定优先级

仓库

  • 本地仓库:local repository
  • 远程仓库:remote repository

mvn install :打包到 target,并把包推送到本地仓库。
mvn deploy:打包到 target,并把包推送到本地仓库,以及推送到远程仓库

一般我们创建了 Maven 项目,Maven 会从远程仓库下载依赖,放入本地仓库中,这个过程比较耗时;
以后本地项目优先从本地仓库读取依赖。

如果远程仓库没有我们想要的依赖,Maven 会在本地仓库生成这个依赖的 XXX.jar.lastUpdatedXXX.pom.lastUpdated,相当于这个依赖的占位符,同时 Maven 报错 Could not find artifact XXX

构建过程

Lifecycle & phase

我们构建一个项目,会经过 验证、编译、测试、打包、部署 等一系列过程。
Maven 用 lifecycle(生命周期) 表示这个构建过程,其中的每一步叫做 phase(阶段)

Plugin & goal

Maven 实际的构建工作,交给 plugin(插件) 完成的。
每个 plugin,由1个或多个 goal 组成。

比如 compiler plugin 有两个 goal

  • compile:编译 scr/main/java 中的源代码
  • testCompile:编译 src/test/java 中的源代码

每个 phase,可以不绑定 goal、可以绑定1个或多个 goal

  • 当不绑定 goal,执行这个 phase 会什么都不做;
  • 当绑定1个或多个 goal,执行这个 phase 会执行所有的 goal

Maven 定义了 3 种生命周期

  • default lifecycle
  • clean lifecycle
  • site lifecycle

这 3 种生命周期,由不同的 phase 组成。
下面是它们的定义(https://maven.apache.org/ref/3.8.7/maven-core/lifecycles.html)。

default lifecycle

default lifecycle 的定义文件中,每个 phase 都没有绑定插件;
因此,这个定义文件仅仅声明了 phase 的执行顺序。

<phases>
  <phase>validate</phase>
  <phase>initialize</phase>
  <phase>generate-sources</phase>
  <phase>process-sources</phase>
  <phase>generate-resources</phase>
  <phase>process-resources</phase>
  <phase>compile</phase>
  <phase>process-classes</phase>
  <phase>generate-test-sources</phase>
  <phase>process-test-sources</phase>
  <phase>generate-test-resources</phase>
  <phase>process-test-resources</phase>
  <phase>test-compile</phase>
  <phase>process-test-classes</phase>
  <phase>test</phase>
  <phase>prepare-package</phase>
  <phase>package</phase>
  <phase>pre-integration-test</phase>
  <phase>integration-test</phase>
  <phase>post-integration-test</phase>
  <phase>verify</phase>
  <phase>install</phase>
  <phase>deploy</phase>
</phases>

每个 phase 绑定什么插件,跟 打包方式 - packaging 有关,由其他文件定义。
联合 default lifecycle 的定义文件,就可以确定不同的 打包方式 下, default lifecycle 每个 phase 的具体操作是什么(可以参看 https://maven.apache.org/ref/3.8.7/maven-core/default-bindings.html)。

打包方式为 pom 绑定的插件
<phases>
  <install>
    org.apache.maven.plugins:maven-install-plugin:2.4:install
  </install>
  <deploy>
    org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
  </deploy>
</phases>
打包方式为 jar 绑定的插件
<phases>
  <process-resources>
    org.apache.maven.plugins:maven-resources-plugin:2.6:resources
  </process-resources>
  <compile>
    org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
  </compile>
  <process-test-resources>
    org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
  </process-test-resources>
  <test-compile>
    org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
  </test-compile>
  <test>
    org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
  </test>
  <package>
    org.apache.maven.plugins:maven-jar-plugin:2.4:jar
  </package>
  <install>
    org.apache.maven.plugins:maven-install-plugin:2.4:install
  </install>
  <deploy>
    org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
  </deploy>
</phases>

clean lifecycle

clean phase 绑定插件:org.apache.maven.plugins:maven-clean-plugin:2.5:clean

<phases>
  <phase>pre-clean</phase>
  <phase>clean</phase>
  <phase>post-clean</phase>
</phases>
<default-phases>
  <clean>
    org.apache.maven.plugins:maven-clean-plugin:2.5:clean
  </clean>
</default-phases>

site lifecycle

site phase 绑定插件:org.apache.maven.plugins:maven-site-plugin:3.3:site
site-deploy phase 绑定插件:org.apache.maven.plugins:maven-site-plugin:3.3:deploy

<phases>
  <phase>pre-site</phase>
  <phase>site</phase>
  <phase>post-site</phase>
  <phase>site-deploy</phase>
</phases>
<default-phases>
  <site>
    org.apache.maven.plugins:maven-site-plugin:3.3:site
  </site>
  <site-deploy>
    org.apache.maven.plugins:maven-site-plugin:3.3:deploy
  </site-deploy>
</default-phases>

命令行

我们可以使用 Maven 命令行,构建我们的项目。
Maven 命令行可以由多个 phase + goal 组合,命令行格式为 mvn xxx xxx

一个 phase 属于一个 lifecycle,在这个 lifecycle 中,该 phase 之前可能有其他 phase
当执行一个 phase ,Maven 会先执行这个 phase 之前所有的 phase,最后再执行这个 phase

举个例子,我们执行命令 mvn clean dependency:copy-dependencies package 会:

  1. 执行 clean lifecycleclean 之前所有的 phase(最后执行 clean);
  2. 执行 dependency:copy-dependencies
  3. 执行 default lifecyclepackage 之前所有的 phase(最后执行 package);

常见的命令行

命令行用途
mvn clean删除当前项目的 target 目录
mvn package根据 pom.xml 配置的 打包方式 对项目打包,打出来的包放在当前项目的 target 目录下
mvn install先打包放在当前项目的 target 目录下,然后把包推送到本地仓库
mvn deploy先打包放在当前项目的 target 目录下,然后把包推送到本地仓库,最后推送到远程仓库
mvn clean install相当于 mvn clean + mvn install
mvn clean deploy相当于 mvn clean + mvn deploy

注:cleanclean lifecycle 的一个 phase, install deploydefault lifecycle 中的 phase,所以他们要组合使用。

可重现构建

可重现构建:Reproducible builds
指只要源码相同,每次构建,都会得到相同的产物。
Maven 实现了 可重现构建

参考资料

  1. https://maven.apache.org/guides/getting-started/index.html

本文完。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

plattoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值