Maven学习笔记

banner

前言

近些日子,笔者在学习Java开发,不得不说,Java的生态圈做的有够大的。前几天Java核心学完了以后,本来以为已经学了挺多内容,可以喘口气,没想到Java core只能算是个入门。后续还有一系列的框架,如MyBatis、Spring、SpringMVC、SpringBoot、SpringCloud等等,数不胜数,磨刀霍霍在等着我。

而在学这些东西时,Maven则始终贯穿其中,虽然说Maven使用上还是挺简单的,但作为一个底层基础工具,本人觉得还是有必要稍微进行一些系统学习,这样才能更好的理解其具体运行过程。这篇博文则是本人对于Maven的学习记录。

Maven概述

Maven是什么,为什么有必要学习它:

Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具。

在我们利用Java语言做开发时,秉承着不重复造轮子的原则(当然更多时候是没能力重复造轮子),必定会涉及到第三方类库的导入,而这些被导入的类,就成为了我们开发工程的“依赖”。显然,这些依赖文件在我们开发乃至部署时,都要先保存在我们写的类的可见范围内。

在简单的开发中,我们只需自行下载所需类库的jar文件,将其移动到指定目录即可。然而,随着时间的推移,开发逐渐转向了“面向框架”的模式,而每个框架又是前人基于一系列的依赖所构建起来的,这就导致了Java工程动辄都有成百上千的依赖,而这些依赖之间也存在着互相依赖的情况。

显然,人类是无法管理这样量级的依赖的,而Maven就是大佬们为了解决这个问题所开发出来的工具(相似的工具还有Gradle,未来有需要再学了)。通过Maven的依赖配置,我们只需指定我们想要的框架或工程,Maven会自动将其所需的依赖进行下载,编译时也无需我们过多关注,全部都可以交由Maven来自动管理。

除了作为依赖管理外,Maven还是一个构建工具。构建是指:在我们开发完成后,将源码打包为可以直接部署的文件。当我们在利用如IDEA这类的IDE开发时,IDE会接管构建过程,以方便我们调试和开发,然而,最终在开发完毕,脱离IDE时,构建过程则是需要利用Maven完成。

简言之,作为依赖管理+工程构建工具,对于学习Java开发来说,Maven不是一个可选项,而是一个必须掌握的基础技能。


Maven部署及基础配置

核心程序

  1. 下载

    在这里可以下载到最新版本的Maven:

    Maven – Download Apache Maven

    下载完毕后直接解压到任意非中文没有空格的目录。

    Maven的核心配置文件为:conf/settings.xml

  2. 指定本地仓库

    默认的Maven仓库在C盘,如果你的C盘空间不够大,那么可以将其修改为其他位置,如下:

    1
    2
    3
    4
    5
    6
    7
    <!-- localRepository
    | The path to the local repository maven will use to store artifacts.
    |
    | Default: ${user.home}/.m2/repository
    <localRepository>/path/to/local/repo</localRepository>
    -->
    <localRepository>D:\maven-repository</localRepository>

    当然,要把标签取消注释。

  3. 配置基础 JDK 版本

    Maven默认的JDK版本为1.5,而我们一般至少要使用1.8,修改方法为:将下面的 profile 标签整个复制到 settings.xml 文件的 profiles 标签内。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <profile>
    <id>jdk-1.8</id>
    <activation>
    <activeByDefault>true</activeByDefault>
    <jdk>1.8</jdk>
    </activation>
    <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>
    </profile>

环境变量配置

上一步下载的源码,要想在命令行中使用,则需要配置环境变量。

  1. 配置 MAVEN_HOME

    新建一个系统变量,变量名为:MAVEN_HOME,变量值为解压的Maven根目录

    TIP
    配置环境变量的规律:
    XXX_HOME 通常指向的是 bin 目录的上一级
    PATH 指向的是 bin 目录

  2. 配置PATH

    在PATH中,新建一个%MAVEN_HOME%\bin,将其指向Maven根目录中的bin文件夹

做完这些,在命令台中使用mvn -v,即可看到Maven的版本信息输出。


Maven核心概念

坐标

Maven通过“坐标”的概念,来精准定位单个jar包文件。

Maven的坐标中有三个维度:

  • groupId:公司或组织的 id
  • artifactId:一个项目或者是项目中的一个模块的 id
  • version:版本号

例如我们有这样一个坐标:

1
2
3
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>

其对应的jar包在本地仓库中的位置为:

1
Maven本地仓库根目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar

pom.xml文件

Maven使用中,最核心的配置文件就是在工程根目录下的pom.xml文件。

  1. 含义:

    POM:Project Object Model,项目对象模型。和 POM 类似的是Web开发中的:DOM(Document Object Model),文档对象模型。它们都是模型化思想的具体体现。

  2. 模型化思想:
    POM表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。

笔者手中的Demo小项目用到的pom.xml长这样,也就是典型的一个配置文件的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gulmall</groupId>
<artifactId>gul-ware</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gul-ware</name>
<description>gul-ware</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>com.gulmall</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

一般而言,我们最常配置的标签就是”dependencies”,只需在Maven官方仓库找到我们要添加的依赖,将其中的dependency标签复制过来即可。


约定的目录结构

现今的开发,有个规则叫“约定大于配置(convention over configuration)”,是一种软件设计范式。

Maven 对于目录结构,就采用这个模式:即没有采用配置的方式,而是基于约定。这样会让我们的开发更方便。

而Maven的约定工程目录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
└───maven-project
├───pom.xml
├───README.txt
├───NOTICE.txt
├───LICENSE.txt
└───src
├───main
│ ├───java
│ ├───resources
│ ├───filters
│ └───webapp
├───test
│ ├───java
│ ├───resources
│ └───filters
├───it
├───site
└───assembly

Maven 为了让构建过程能够尽可能自动化完成,约定了目录结构的作用。例如:Maven 执行编译操作,必须先去 Java 源程序目录读取 Java 源代码,然后执行编译,最后把编译结果存放在 target 目录。


Maven使用基础

构建/生命周期命令

  1. 清理操作

    mvn clean

    效果:删除 target 目录

  2. 编译操作

    主程序编译:mvn compile

    测试程序编译:mvn test-compile

    主体程序编译结果存放的目录:target/classes

    测试程序编译结果存放的目录:target/test-classes

  3. 测试操作

    mvn test

    测试的报告存放的目录:target/surefire-reports

  4. 打包操作

    mvn package

    打包的结果——jar 包或者war 包,存放的目录:target

  5. 安装操作

    mvn install

    安装的效果是将本地构建过程中生成的 jar 包存入 Maven 本地仓库。这个 jar 包在 Maven 仓库中的路径是根据它的坐标生成的。

    另外,安装操作还会将 pom.xml 文件转换为 XXX.pom 文件一起存入本地仓库。所以我们在 Maven 的本地仓库中想看一个 jar 包原始的 pom.xml 文件时,查看对应 XXX.pom 文件即可,它们是名字发生了改变,本质上是同一个文件。

除了这些生命周期命令外,还有构建工程用的archetype命令,这里不赘述了,用IDEA操作很简单。


依赖范围

标签的位置:dependencies/dependency/scope

标签的可选值:compile/test/provided/system/runtime/import

这里主要只关注:compile/test/provided

compile:通常实际运行时真正要用到的 jar 包都是以 compile 范围导入依赖。比如 SSM 框架所需jar包。

test:测试过程中使用的 jar 包,无需在正式环境中使用,就以 test 范围导入依赖。比如 junit。

provided:在开发过程中需要用到的“服务器上的 jar 包”通常以 provided 范围导入依赖。比如 servlet-api、jsp-api。而这个范围的 jar 包不参与部署、不放进 war 包,以避免和服务器上已有的同类 jar 包产生冲突,同时减轻服务器的负担。


依赖传递性

  1. 概念

    A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?

    如果可以,则体现了依赖的传递性。

  2. 传递原则

    如果 B 依赖 C 时使用的是 compile 范围:可以传递

    如果 B 依赖 C 时使用 test 或 provided 范围:不能传递,所以在 A 中需要 C 这样的 jar 包时,就必须在需要的地方明确配置依赖才可以。


依赖排除

  1. 概念

    当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。

    所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。

  2. 配置方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependency>
    <groupId>com.atguigu.maven</groupId>
    <artifactId>pro01-maven-java</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>compile</scope>
    <!-- 使用excludes标签配置依赖的排除 -->
    <exclusions>
    <!-- 在exclude标签中配置一个具体的排除 -->
    <exclusion>
    <!-- 指定要排除的依赖的坐标(不需要写version) -->
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    </exclusion>
    </exclusions>
    </dependency>

继承

  1. 概念

    Maven工程之间,A 工程可以继承 B 工程

    • B 工程:父工程
    • A 工程:子工程

    本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置。

  2. 作用

在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。

它的背景是:

  • 对一个比较大型的项目进行了模块拆分。

  • 一个 project 下面,创建了很多个 module。

  • 每一个 module 都需要配置自己的依赖信息。

它背后的需求是:

  • 在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理。

  • 使用同一个框架内的不同 jar 包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。

  • 使用框架时所需要的 jar包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。

  1. 操作

    • ① 创建父工程

      工程名称:pro03-maven-parent

      工程创建好之后,要修改它的打包方式:

      1
      2
      3
      4
      5
      <groupId>com.atguigu.maven</groupId>
      <artifactId>pro03-maven-parent</artifactId>
      <version>1.0-SNAPSHOT</version>
      <!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
      <packaging>pom</packaging>
    • ② 创建模块工程

      模块工程类似于 IDEA 中的 module,所以需要进入 pro03-maven-parent 工程的根目录,然后运行 mvn archetype:generate 命令来创建模块工程。

      假设,我们创建三个模块工程。

    • ③ 查看被添加新内容的父工程 pom.xml
      下面 modules 和 module 标签是聚合功能的配置

      1
      2
      3
      4
      5
      <modules>  
      <module>pro04-maven-module</module>
      <module>pro05-maven-module</module>
      <module>pro06-maven-module</module>
      </modules>
    • ④ 解读子工程的pom.xml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <!-- 使用parent标签指定当前工程的父工程 -->
      <parent>
      <!-- 父工程的坐标 -->
      <groupId>com.atguigu.maven</groupId>
      <artifactId>pro03-maven-parent</artifactId>
      <version>1.0-SNAPSHOT</version>
      </parent>

      <!-- 子工程的坐标 -->
      <!-- 如果子工程坐标中的groupId和version与父工程一致,那么可以省略 -->
      <!-- <groupId>com.atguigu.maven</groupId> -->
      <artifactId>pro04-maven-module</artifactId>
      <!-- <version>1.0-SNAPSHOT</version> -->
    • ⑤ 在父工程中配置依赖的统一管理

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      <!-- 使用dependencyManagement标签配置对依赖的管理 -->
      <!-- 被管理的依赖并没有真正被引入到工程 -->
      <dependencyManagement>
      <dependencies>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.0.0.RELEASE</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.0.0.RELEASE</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.0.0.RELEASE</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>4.0.0.RELEASE</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.0.0.RELEASE</version>
      </dependency>
      </dependencies>
      </dependencyManagement>
    • ⑥ 子工程中引用那些被父工程管理的依赖

    关键点:省略版本号

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。	-->
    <!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 -->
    <!-- 具体来说是由父工程的dependencyManagement来决定。 -->
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    </dependency>
    </dependencies>
    • ⑦ 在父工程中升级依赖信息的版本

      1
      2
      3
      4
      5
      6
      7
      ……
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.1.4.RELEASE</version>
      </dependency>
      ……
    • ⑧ 在父工程中声明自定义属性

      1
      2
      3
      4
      5
      6
      7
      <!-- 通过自定义属性,统一指定Spring的版本 -->
      <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

      <!-- 自定义标签,维护Spring版本数据 -->
      <atguigu.spring.version>4.3.6.RELEASE</atguigu.spring.version>
      </properties>

      在需要的地方使用${}的形式来引用自定义的属性名:

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${atguigu.spring.version}</version>
      </dependency>
  2. 实际意义

    编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。

聚合

  1. 意义:

    使用一个“总工程”将各个“模块工程”汇集起来,作为一个整体对应完整的项目。

    项目:整体
    模块:部分

    TIP

    概念的对应关系:

    从继承关系角度来看:

    • 父工程
    • 子工程

    从聚合关系角度来看:

    • 总工程
    • 模块工程
  2. 好处:

    • 一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。

    • 以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。

    • 配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。

  3. 聚合的配置:

    在总工程中配置 modules 即可:

    1
    2
    3
    4
    5
    <modules>  
    <module>pro04-maven-module</module>
    <module>pro05-maven-module</module>
    <module>pro06-maven-module</module>
    </modules>

其他核心概念

  1. 生命周期

    纯理论的内容,先不写了

  2. 插件和目标

    Maven 的核心程序仅仅负责宏观调度,不做具体工作。具体工作都是由 Maven 插件完成的。例如:编译就是由 maven-compiler-plugin-3.1.jar 插件来执行的。

    一个插件可以对应多个目标,而每一个目标都和生命周期中的某一个环节对应。

    Default 生命周期中有 compile 和 test-compile 两个和编译相关的环节,这两个环节对应 compile 和 test-compile 两个目标,而这两个目标都是由 maven-compiler-plugin-3.1.jar 插件来执行的。

  3. 仓库

    分为本地和远程仓库。


参考资料

尚硅谷2022版Maven教程(maven入门+高深,全网无出其右!)