Maven 基础
Maven 基础篇
[!TIP]
课程内容:
- 初始 Maven
- Maven 概述
- Maven 模型
- Maven 仓库介绍
- Maven 安装与配置
- IDEA 集成 Maven
- 依赖管理
- 单元测试
初始 Maven
介绍

Maven 是一款用于管理和构建 Java 项目的工具,是 Apache 旗下的一个开源项目 。
Apache 软件基金会,成立于 1999 年 7 月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利性组织。
开源项目:https://www.apache.org/index.html#projects-list
那我们之前在 JavaSE 阶段,没有使用 Maven,依然可以构建 Java 项目。 我们为什么现在还要学习 Maven 呢 ? 那接下来,我们就来聊聊 Maven 的作用。
Maven 的作用

依赖管理
方便快捷的管理项目依赖的资源(jar 包),避免版本冲突问题。
1). 使用 maven 前
我们项目中要想使用某一个 jar 包,就需要把这个 jar 包从官方网站下载下来,然后再导入到项目中。然后在这个项目中,就可以使用这个 jar 包了。

2). 使用 maven 后
当使用 maven 进行项目依赖(jar 包)管理,则很方便的可以解决这个问题。 我们只需要在 maven 项目的 pom.xml 文件中,添加一段如下图所示的配置即可实现。

在 maven 项目的配置文件中,加入上面这么一段配置信息之后,maven 会自动的根据配置信息的描述,去下载对应的依赖。 然后在项目中,就可以直接使用了。
项目构建
Maven 还提供了标准化的跨平台的自动化构建方式。

如上图所示我们开发了一套系统,代码需要进行编译、测试、打包、发布等过程,这些操作是所有项目中都需要做的,如果需要反复进行就显得特别麻烦,而 Maven 提供了一套简单的命令来完成项目构建。

通过 Maven 中的命令,就可以很方便的完成项目的编译(compile)、测试(test)、打包(package)、发布(deploy) 等操作。
而且这些操作都是跨平台的,也就是说无论你是 Windows 系统,还是 Linux 系统,还是 Mac 系统,这些命令都是支持的。
统一项目结构
Maven 还提供了标准、统一的项目结构 。
1). 未使用 Maven
由于 java 的开发工具呢,有很多,除了大家熟悉的 IDEA 以外,还有像早期的 Eclipse、MyEclipse。而不同的开发工具,创建出来的 java 项目的目录结构是存在差异的,那这就会出现一个问题。
Eclipse 创建的 java 项目,并不能直接导入 IDEA 中。 IDEA 创建的 java 项目,也没有办法直接导入到 Eclipse 中。

2). 使用 Maven
而如果我们使用了 Maven 这一款项目构建工具,它给我们提供了一套标准的 java 项目目录。如下所示:

也就意味着,无论我们使用的是什么开发工具,只要是基于 maven 构建的 java 项目,最终的目录结构都是相同的,如图所示。 那这样呢,我们使用 Eclipse、MyEclipse、IDEA 创建的 maven 项目,就可以在各个开发工具直接直接导入使用了,更加方便、快捷。

而在上面的 maven 项目的目录结构中,main 目录下存放的是项目的源代码,test 目录下存放的是项目的测试代码。 而无论是在 main 还是在 test 下,都有两个目录,一个是 java,用来存放源代码文件;另一个是 resources,用来存放配置文件。
最后呢,一句话总结一下什么是 Maven。 Maven 就是一款管理和构建 java 项目的工具。
Maven 的内容,我们进行了分层的设计和讲解,分为两个部分:Maven 核心和 Maven 进阶。 那今天,我们先来讲解 Maven 核心部分的内容,在 Web 开发的最后,我们再来讲解 Maven 进阶部分的内容。
Maven 概述
Maven 介绍
Apache Maven 是一个项目管理和构建工具,它基于项目对象模型(Project Object Model , 简称: POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档。

Maven 的作用:
- 方便的依赖管理
- 统一的项目结构
- 标准的项目构建流程
Maven 模型
- 项目对象模型 (Project Object Model)
- 依赖管理模型(Dependency)
- 构建生命周期/阶段(Build lifecycle & phases)
1). 构建生命周期/阶段(Build lifecycle & phases)

以上图中紫色框起来的部分,就是用来完成标准化构建流程 。当我们需要编译,Maven 提供了一个编译插件供我们使用;当我们需要打包,Maven 就提供了一个打包插件供我们使用等。
2). 项目对象模型 (Project Object Model)

以上图中紫色框起来的部分属于项目对象模型,就是将我们自己的项目抽象成一个对象模型,有自己专属的坐标,如下图所示是一个 Maven 项目:

坐标,就是资源(jar 包)的唯一标识,通过坐标可以定位到所需资源(jar 包)位置。
坐标的组成部分:
- groupId: 组织名
- arfitactId: 模块名
- Version: 版本号
3). 依赖管理模型(Dependency)

以上图中紫色框起来的部分属于依赖管理模型,是使用坐标来描述当前项目依赖哪些第三方 jar 包。

之前我们项目中需要 jar 包时,直接就把 jar 包复制到项目下的 lib 目录,而现在我们只需要在 pom.xml 中配置依赖的配置文件即可。 而这个依赖对应的 jar 包其实就在我们本地电脑上的 maven 仓库中。
如下图,就是老师本地的 maven 仓库中的 jar 文件:

Maven 仓库
仓库:用于存储资源,管理各种 jar 包
仓库的本质就是一个目录(文件夹),这个目录被用来存储开发中所有依赖(就是 jar 包)和插件
Maven 仓库分为:
- 本地仓库:自己计算机上的一个目录(用来存储 jar 包)
- 中央仓库:由 Maven 团队维护的全球唯一的。仓库地址:https://repo1.maven.org/maven2/
- 远程仓库(私服):一般由公司团队搭建的私有仓库

当项目中使用坐标引入对应依赖 jar 包后,
-
首先会查找本地仓库中是否有对应的 jar 包
- 如果有,则在项目直接引用
- 如果没有,则去中央仓库中下载对应的 jar 包到本地仓库
-
如果还可以搭建远程仓库(私服),将来 jar 包的查找顺序则变为: 本地仓库 --> 远程仓库–> 中央仓库
Maven 安装
认识了 Maven 后,我们就要开始使用 Maven 了,那么首先我们要进行 Maven 的下载与安装。
下载
- 下载地址:https://maven.apache.org/download.cgi
- 在提供的资料中,已经提供了下载好的安装包。如下:
安装步骤
Maven 安装配置步骤:
- 解压安装
- 配置仓库
- 配置阿里云私服
- 配置 Maven 环境变量
1). 解压 apache-maven-3.9.4-bin.zip(解压即安装)
建议解压到没有中文、特殊字符的路径下。如课程中解压到 E:\develop 下。
解压缩后的目录结构如下:

- bin 目录 : 存放的是可执行命令。(mvn 命令重点关注)
- conf 目录 :存放 Maven 的配置文件。(settings.xml 配置文件后期需要修改)
- lib 目录 :存放 Maven 依赖的 jar 包。(Maven 也是使用 java 开发的,所以它也依赖其他的 jar 包)
2). 配置本地仓库
- 在自己计算机上新一个目录(本地仓库,用来存储 jar 包)

- 进入到 conf 目录下修改
settings.xml配置文件- 使用超级记事本软件,打开 settings.xml 文件,定位到 53 行
- 复制
<localRepository>标签,粘贴到注释的外面(55 行) - 复制之前新建的用来存储 jar 包的路径,替换掉
<localRepository>标签体内容

3). 配置阿里云私服
由于中央仓库在国外,所以下载 jar 包速度可能比较慢,而阿里公司提供了一个远程仓库,里面基本也都有开源项目的 jar 包。
进入到 conf 目录下修改 settings.xml 配置文件:
- 使用超级记事本软件,打开 settings.xml 文件,定位到 160 行左右
- 在
<mirrors>标签下为其添加子标签<mirror>,内容如下:
1 | <mirror> |
注意配置的位置,在 <mirrors> ... </mirrors> 中间添加配置。如下图所示:

4).** 配置环境****变量**
Maven 环境变量的配置类似于 JDK 环境变量配置一样
- 在系统变量处新建一个变量 MAVEN_HOME。 MAVEN_HOME 环境变量的值,设置为 maven 的解压安装目录

- 在 Path 中进行配置。 PATH 环境变量的值,设置为:%MAVEN_HOME%\bin

- 打开 DOS 命令提示符进行验证,出现如图所示表示安装成功 。
命令为:mvn -v

5). 配置关联的 JDK版本(可选)
进入到 conf 目录下修改 settings.xml 配置文件,在 <profiles> </profiles> 中增加如下配置:
1 | <profile> |
IDEA 集成 Maven
我们要想在 IDEA 中使用 Maven 进行项目构建,就需要在 IDEA 中集成 Maven,那么就需要在 IDEA 中配置与 maven 的关联。
创建 Maven 项目
全局设置
- 进入 IDEA 的欢迎页面
选择 IDEA 中 File =>close project=>Customize=>All settings


- 打开 All settings , 选择
Build,Execution,Deployment=>Build Tools=>Maven

- 配置工程的编译版本为 17

这里所设置的 maven 的环境信息,并未指定任何一个 project,此时设置的信息就属于全局配置信息。 以后,我们再创建 project,默认就是使用我们全局配置的信息。
创建项目
- 创建一个空项目,命名为 web-project01

- 创建好项目之后,进入项目中,要设置 JDK 的版本号。选择小齿轮,选择 Project Structure

- 创建模块,选择 Java 语言,选择 Maven。 填写模块的基本信息


- 在 maven 项目中,创建 HelloWorld 类,并运行

[!TIP]
Maven 项目的目录结构:
maven-project01
|— src (源代码目录和测试代码目录)
|— main (源代码目录)
|— java (源代码 java 文件目录)
|— resources (源代码配置文件目录)
|— test (测试代码目录)
|— java (测试代码 java 目录)
|— resources (测试代码配置文件目录)
|— target (编译、打包生成文件存放目录)
pom 文件详解
POM (Project Object Model) :指的是项目对象模型,用来描述当前的 maven 项目。
- 使用 pom.xml 文件来描述当前项目。 pom.xml 文件如下:
1 | _<?_xml version="1.0" encoding="UTF-8"_?>_ |
pom 文件详解:
-
<project>``:pom 文件的根标签,表示当前 maven 项目 -
<modelVersion>:声明项目描述遵循哪一个 POM 模型版本- 虽然模型本身的版本很少改变,但它仍然是必不可少的。目前 POM 模型版本是 4.0.0
-
坐标 :
<groupId><artifactId><version>- 定位项目在本地仓库中的位置,由以上三个标签组成一个坐标
-
<maven.compiler.source>:编译 JDK 的版本 -
<maven.compiler.target>:运行 JDK 的版本 -
<project.build.sourceEncoding>: 设置项目的字符集
Maven 坐标
什么是坐标?
- Maven 中的坐标是资源****的唯一标识 , 通过该坐标可以唯一定位资源位置
- 使用坐标来定义项目或引入项目中需要的依赖
Maven 坐标主要组成:
-
groupId:定义当前 Maven 项目隶属组织名称(通常是域名反写,例如:com.itheima)
-
artifactId:定义当前 Maven 项目名称(通常是模块名称,例如 order-service、goods-service)
-
version:定义当前项目版本号
- SNAPSHOT: 功能不稳定、尚处于开发中的版本,即快照版本
- RELEASE: 功能趋于稳定、当前更新停止,可以用于发行的版本
如下图就是使用坐标表示一个项目:

注意:
- 上面所说的资源可以是插件、依赖、当前项目。
- 我们的项目如果被其他的项目依赖时,也是需要坐标来引入的。
导入 Maven 项目
在 IDEA 中导入 Maven 项目,有两种方式。
- 方式一:
File->Project Structure->Modules->Import Module->选择maven项目的pom.xml。

- 方式二:
Maven面板->+(Add Maven Projects)->选择maven项目的pom.xml。

依赖管理
依赖配置
基本配置
依赖:指当前项目运行所需要的 jar 包。一个项目中可以引入多个依赖:
例如:在当前工程中,我们需要用到 logback 来记录日志,此时就可以在 maven 工程的 pom.xml 文件中,引入 logback 的依赖。具体步骤如下:
- 在 pom.xml 中编写
<dependencies>标签 - 在
<dependencies>标签中使用<dependency>引入坐标 - 定义坐标的
groupId、artifactId、version
1 | <dependencies> |
- 点击刷新按钮,引入最新加入的坐标
刷新依赖:保证每一次引入新的依赖,或者修改现有的依赖配置,都可以加入最新的坐标

[!TIP]
注意事项:
- 如果引入的依赖,在本地仓库中不存在,将会连接远程仓库 / 中央仓库,然后下载依赖(这个过程会比较耗时,耐心等待)
- 如果不知道依赖的坐标信息,可以到 mvn 的中央仓库(https://mvnrepository.com/)中搜索
查找依赖
- 利用中央仓库搜索的依赖坐标,以常见的 logback-classic 为例。

- 利用 IDEA 工具搜索依赖,以常见的 logback-classic 为例。

- 熟练上手 maven 后,快速导入依赖,以常见的 logback-classic 为例。

依赖传递
我们上面在 pom.xml 中配置了一项依赖,就是 spring-context,但是我们通过右侧的 maven 面板可以看到,其实引入进来的依赖,并不是这一项,有非常多的依赖,都引入进来了。我们可以看到如下图所示:

为什么会出现这样的现象呢? 那这里呢,就涉及到 maven 中非常重要的一个特性,那就是 Maven 中的依赖传递。
所谓 maven 的依赖传递,指的就是如果在 maven 项目中,A 依赖了 B,B 依赖了 C,C 依赖了 D,那么在 A 项目中,也会有 C、D 依赖,因为依赖会传递。
那如果,传递下来的依赖,在项目开发中,我们确实不需要,此时,我们可以通过 Maven 中的排除依赖功能,来将这个依赖排除掉。
排除依赖

- 排除依赖:指主动断开依赖的资源,被排除的资源无需指定版本。
- 配置形式如下:
1 | <dependency> |
依赖排除示例:
- 默认通过 maven 的依赖传递,传递下来了
micrometer-observation的依赖。

- 加入排除依赖的配置之后,该依赖就被排除掉了。

生命周期
介绍
Maven 的生命周期就是为了对所有的构建过程进行抽象和统一。 描述了一次项目构建,经历哪些阶段。
在 Maven 出现之前,项目构建的生命周期就已经存在,软件开发人员每天都在对项目进行清理,编译,测试及部署。虽然大家都在不停地做构建工作,但公司和公司间、项目和项目间,往往使用不同的方式做类似的工作。
Maven 从大量项目和构建工具中学习和反思,然后总结了一套高度完美的,易扩展的项目构建生命周期。这个生命周期包含了项目的清理,初始化,编译,测试,打包,集成测试,验证,部署和站点生成等几乎所有构建步骤。
Maven 对项目构建的生命周期划分为 3 套(相互独立):

- clean:清理工作。
- default:核心工作。如:编译、测试、打包、安装、部署等。
- site:生成报告、发布站点等。
三套生命周期又包含哪些具体的阶段呢, 我们来看下面这幅图:

每套生命周期包含一些阶段(phase),阶段是有顺序的,后面的阶段依赖于前面的阶段。
我们看到这三套生命周期,里面有很多很多的阶段,这么多生命周期阶段,其实我们常用的并不多,主要关注以下几个:
- clean:移除上一次构建生成的文件
- compile:编译项目源代码
- test:使用合适的单元测试框架运行测试(junit)
- package:将编译后的文件打包,如:jar、war 等
- install:安装项目到本地仓库
Maven 的生命周期是抽象的,这意味着生命周期本身不做任何实际工作。在 Maven 的设计中,实际任务(如源代码编译)都交由插件来完成。

IDEA 工具为了方便程序员使用 maven 生命周期,在右侧的 maven 工具栏中,已给出快速访问通道。

- 生命周期的顺序是:
clean-->validate-->compile-->test-->package-->verify-->install-->site-->deploy - 我们需要关注的就是:
clean-->compile-->test-->package-->install
[!TIP]
**说明:**在同一套生命周期中,我们在执行后面的生命周期时,前面的生命周期都会执行。
[!TIP]
**思考:**当运行 package 生命周期时,clean、compile 生命周期会不会运行?
clean 不会运行,compile 会运行。 因为 compile 与 package 属于同一套生命周期,而 clean 与 package 不属于同一套生命周期。
执行
在日常开发中,当我们要执行指定的生命周期时,有两种执行方式:
- 在 idea 工具右侧的 maven 工具栏中,选择对应的生命周期,双击执行
- 在 DOS 命令行中,通过 maven 命令执行
方式一:在 idea 中执行生命周期
- 选择对应的生命周期,双击执行

其他的生命周期都是类似的道理,双击运行即可。
方式二:在命令行中执行生命周期
- 打开 maven 项目对应的磁盘目录

- 在当前目录下打开 CMD

类似的道理,我们也可以在命令执行:
- mvn compile
- mvn test
- mvn package
- mvn install
单元测试
介绍
**测试:**是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。
**阶段划分:**单元测试、集成测试、系统测试、验收测试。

1). 单元测试
- 介绍:对软件的基本组成单位进行测试,最小测试单位。
- 目的:检验软件基本组成单位的正确性。
- 测试人员:开发人员
2). 集成测试
- 介绍:将已分别通过测试的单元,按设计要求组合成系统或子系统,再进行的测试。
- 目的:检查单元之间的协作是否正确。
- 测试人员:开发人员
3). 系统测试
- 介绍:对已经集成好的软件系统进行彻底的测试。
- 目的:验证软件系统的正确性、性能是否满足指定的要求。
- 测试人员:测试人员
4). 验收测试
- 介绍:交付测试,是针对用户需求、业务流程进行的正式的测试。
- 目的:验证软件系统是否满足验收标准。
- 测试人员:客户/需求方
**测试方法:**白盒测试、黑盒测试 及 灰盒测试。

1). 白盒测试
清楚软件内部结构、代码逻辑。
用于验证代码、逻辑正确性。
2). 黑盒测试
不清楚软件内部结构、代码逻辑。
用于验证软件的功能、兼容性、验收测试等方面。
3). 灰盒测试
结合了白盒测试和黑盒测试的特点,既关注软件的内部结构又考虑外部表现(功能)。

Junit 入门

单元测试
- 单元测试:就是针对最小的功能单元(方法),编写测试代码对其正确性进行测试。
- JUnit:最流行的 Java 测试框架之一,提供了一些功能,方便程序进行单元测试(第三方公司提供)。
在之前的课程中,我们进行程序的测试 ,都是 main 方法中进行测试 。如下图所示:

通过 main 方法是可以进行测试的,可以测试程序是否正常运行。但是 main 方法进行测试时,会存在如下问题:
- 测试代码与源代码未分开,难维护。
- 一个方法测试失败,影响后面方法。
- 无法自动化测试,得到测试报告。
而如果我们使用了 JUnit 单元测试框架进行测试,将会有以下优势:
- 测试代码与源代码分开,便于维护。
- 可根据需要进行自动化测试。
- 可自动分析测试结果,产出测试报告。

入门程序
需求:使用 JUnit,对 UserService 中的业务方法进行单元测试,测试其正确性。
- 在
pom.xml中,引入 JUnit 的依赖。
1 | <!--Junit单元测试依赖--> |
- 在 test/java 目录下,创建测试类,并编写对应的测试方法,并在方法上声明 @Test 注解。
1 |
|
- 运行单元测试 (测试通过:绿色;测试失败:红色)。
- 测试通过显示绿色

- 测试失败显示红色

[!TIP]
** 注意:**
- 测试类的命名规范为:XxxxTest
- 测试方法的命名规定为:public void xxx(){…}
断言
JUnit 提供了一些辅助方法,用来帮我们确定被测试的方法是否按照预期的效果正常工作,这种方式称为断言。

示例演示:
1 | package com.itheima; |
测试结果输出:

常见注解
在 JUnit 中还提供了一些注解,还增强其功能,常见的注解有以下几个:

演示 @BeforeEach,@AfterEach,@BeforeAll,@AfterAll 注解:
1 | public class UserServiceTest { |
输出结果如下:

演示 @ParameterizedTest ,@ValueSource ,@DisplayName 注解:
1 | package com.itheima; |
输出结果如下:

**思考: 在 maven 项目中,test 目录存放单元测试的代码,是否可以在 main 目录中编写单元测试呢 ? **可以,但是不规范

依赖范围
依赖的 jar 包,默认情况下,可以在任何地方使用,在 main 目录下,可以使用;在 test 目录下,也可以使用。
在 maven 中,如果希望限制依赖的使用范围,可以通过 <scope>…</scope> 设置其作用范围。

作用范围:
- 主程序范围有效。(main 文件夹范围内)
- 测试程序范围有效。(test 文件夹范围内)
- 是否参与打包运行。(package 指令范围内)

可以在 pom.xml 中配置 <scope></scope> 属性来控制依赖范围。

如果对 Junit 单元测试的依赖,设置了 scope 为 test,就代表,该依赖,只是在测试程序中可以使用,在主程序中是无法使用的。所以我们会看到如下现象:

如上图所示,给 junit 依赖通过 scope 标签指定依赖的作用范围。 那么这个依赖就只能作用在测试环境,其他环境下不能使用。
scope 的取值常见的如下:

Maven 常见问题

- **问题现象:**Maven 项目中添加的依赖,未正确下载,造成右侧 Maven 面板中的依赖报红,再次 reload 重新加载也不会再下载。
- 产生原因:由于网络原因,依赖没有下载完整导致的,在 maven 仓库中生成了 xxx.lastUpdated文件,该文件不删除,不会再重新下载。

解决方案:
- 根据 maven 依赖的坐标,找到仓库中对应的 xxx.lastUpdated 文件,删除,删除之后重新加载项目即可。
- 通过命令 (*del /s .lastUpdated) 批量递归删除指定目录下的 xxx.lastUpdated 文件,删除之后重新加载项目即可。
- 重新加载依赖,依赖下载了之后,maven 面板可能还会报红,此时可以关闭 IDEA,重新打开 IDEA 加载此项目即可。
为了使大家能够方便的解决这个问题,大家可以将资料中提供的 del.bat 批处理脚本,拷贝到 maven 的安装目录下。 双击这个文件,就可以递归删除该目录下所有的 xxx.lastUpdated 文件。 放置目录如下所示:

- 附件(上述提到的 del.bat 批处理文件, 也可以直接点此下载):
Maven高级篇
我们讲到 Maven 是一款构建和管理 Java 项目的工具。经过前面十几天 web 开发的学习,相信大家对于 Maven 这款工具的基本使用应该没什么问题了。我们掌握了 Maven 工具的基本使用之后,其实对于一些简单的项目的构建及管理基本上就没什么问题了。
但是如果我们需要开发一些中大型的项目,此时仅凭我们前面所学习的 Maven 的基础知识就比较难以应对了。所以我们接下来还需要学习 Maven 提供的一些高级的功能,这些功能在构建和管理 Java 项目的时候用的也是非常多的。
[!TIP]
Maven 高级内容包括:
- 分模块设计与开发
- 继承与聚合
- 私服
分模块设计与开发
介绍
所谓分模块设计,顾名思义指的就是我们在设计一个 Java 项目的时候,将一个 Java 项目拆分成多个模块进行开发。
1). 未分模块设计的问题

如果项目不分模块,也就意味着所有的业务代码是不是都写在这一个 Java 项目当中。随着这个项目的业务扩张,项目当中的业务功能可能会越来越多。
假如我们开发的是一个大型的电商项目,里面可能就包括了商品模块的功能、搜索模块的功能、购物车模块、订单模块、用户中心等等。这些所有的业务代码我们都在一个 Java 项目当中编写。
此时大家可以试想一下,假如我们开发的是一个大型的电商网站,这个项目组至少几十号甚至几百号开发人员,这些开发人员全部操作这一个 Java 项目。此时大家就会发现我们项目管理和维护起来将会非常的困难。而且大家再来看,假如在我们的项目当中,我们自己定义了一些通用的工具类以及通用的组件,而公司还有其他的项目组,其他项目组也想使用我们所封装的这些组件和工具类,其实是非常不方便的。因为 Java 项目当中包含了当前项目的所有业务代码,所以就造成了这里面所封装的一些组件会难以复用。
[!TIP]
总结起来,主要两点问题:不方便项目的维护和管理、项目中的通用组件难以复用。
2). 分模块设计
分模块设计我们在进行项目设计阶段,就可以将一个大的项目拆分成若干个模块,每一个模块都是独立的。

比如我们可以将商品的相关功能放在商品模块当中,搜索的相关业务功能我都封装在搜索模块当中,还有像购物车模块、订单模块。而为了组件的复用,我们也可以将项目当中的实体类、工具类以及我们定义的通用的组件都单独的抽取到一个模块当中。
如果当前这个模块,比如订单模块需要用到这些实体类以及工具类或者这些通用组件,此时直接在订单模块当中引入工具类的坐标就可以了。这样我们就将一个项目拆分成了若干个模块儿,这就是分模块儿设计。
分模块儿设计之后,大家再来看。我们在进行项目管理的时候,我就可以几个人一组,几个人来负责订单模块儿,另外几个人来负责购物车模块儿,这样更加便于项目的管理以及项目的后期维护。
而且分模块设计之后,如果我们需要用到另外一个模块的功能,我们直接依赖模块就可以了。比如商品模块、搜索模块、购物车订单模块都需要依赖于通用组件当中封装的一些工具类,我只需要引入通用组件的坐标就可以了。
[!TIP]
分模块设计就是将项目按照功能/结构拆分成若干个子模块,方便项目的管理维护、拓展,也方便模块键的相互调用、资源共享。
策略
- 策略一:按照功能模块拆分,比如:公共组件、商品模块、搜索模块、购物车模块、订单模块等。
- 策略二:按层拆分,比如:公共组件、实体类、控制层、业务层、数据访问层。
- 策略三:按照功能模块 + 层拆分。

实践
分析
好,我们明白了什么是分模块设计以及分模块设计的优势之后,接下来我们就来看一下我们之前所开发的案例工程。
我们可以看到在这个项目当中,除了我们所开发的部门管理以及员工管理、登录认证等相关业务功能以外,我们是不是也定义了一些实体类,也就是 pojo 包下存放的一些类,像分页结果的封装类 PageBean、 统一响应结果 Result,我们还定义了一些通用的工具类,像 Jwts、阿里云 OSS 操作的工具类等等。
如果在当前公司的其他项目组当中,也想使用我们所封装的这些公共的组件,该怎么办?大家可以思考一下。
-
方案一:直接依赖我们当前项目
tlias-web-management,但是存在两大缺点:- 这个项目当中包含所有的业务功能代码,而想共享的资源,仅仅是 pojo 下的实体类,以及 utils 下的工具类。如果全部都依赖进来,项目在启动时将会把所有的类都加载进来,会影响性能。
- 如果直接把这个项目都依赖进来了,那也就意味着我们所有的业务代码都对外公开了,这个是非常不安全的。
-
方案二:分模块设计
- 将 pojo 包下的实体类,抽取到一个 maven 模块中
tlias-pojo - 将 utils 包下的工具类,抽取到一个 maven 模块中
tlias-utils - 其他的业务代码,放在
tlias-web-management这个模块中,在该模块中需要用到实体类 pojo、工具类 utils,直接引入对应的依赖即可。
- 将 pojo 包下的实体类,抽取到一个 maven 模块中

[!TIP]
注意:分模块开发需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分。
PS:当前我们是为了演示分模块开发,所以是基于我们前面开发的案例项目进行拆分的,实际中都是分模块设计,然后再开发的。
实现
思路我们分析完毕,接下来,我们就根据我们分析的思路,按照如下模块进行拆分:
**1. **创建 maven 模块 tlias-pojo,存放实体类
A. 创建一个正常的 Maven 模块,模块名 tlias-pojo

B. 然后在 tlias-pojo 中创建一个包 com.itheima.pojo (和原来案例项目中的 pojo 包名一致)

C. 将原来案例项目 tlias-web-management 中的 pojo 包下的实体类,复制到 tlias-pojo 模块中

D. 在 tlias-pojo 模块的 pom.xml 文件中引入依赖
1 | <dependencies> |
因为在实体类中,还用到了 Spring 框架中的 @DateTimeFormat 这样的注解,所以这里再引入一个 springboot 的基础起步依赖。
E. 删除原有案例项目 tlias-web-management 的 pojo 包【直接删除不要犹豫,我们已经将该模块拆分出去了】,然后在 pom.xml 中引入 tlias-pojo 的依赖
1 | <dependency> |
2. 创建 Maven 模块 tlias-utils,存放相关工具类
A. 创建一个正常的 Maven 模块,模块名 tlias-utils

B. 然后在 tlias-utils 中创建一个包 com.itheima.utils (和原来案例项目中的 utils 包名一致)

C. 将原来案例项目 tlias-web-management 中的 util 包下的实体类,复制到 tlias-utils 模块中

D. 在 tlias-utils 模块的 pom.xml 文件中引入依赖。
1 | <dependencies> |
E. 删除原有案例项目 tlias-web-management 的 util 包【直接删除不要犹豫,我们已经将该模块拆分出去了】,然后在 pom.xml 中引入 tlias-utils 的依赖。
1 | <dependency> |
到此呢,就已经完成了模块的拆分,拆分出了 tlias-pojo、tlias-utils、tlias-web-management ,如果其他项目中需要用到 pojo,或者 utils 工具类,就可以直接引入依赖。
[!TIP]
- **什么是分模块设计:**将项目按照功能拆分成若干个子模块
- **为什么要分模块设计:**方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享
- **注意事项:**分模块设计需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分
继承与聚合
在案例项目分模块开发之后啊,我们会看到 tlias-pojo、tlias-utils、tlias-web-management 中都引入了一个依赖 lombok 的依赖。我们在三个模块中分别配置了一次。

如果是做一个大型的项目,这三个模块当中重复的依赖可能会很多很多。如果每一个 Maven 模块里面,我们都来单独的配置一次,功能虽然能实现,但是配置是比较 繁琐 的 。
而接下来我们要讲解的 Maven 的继承用来解决这问题的。
继承
我们可以再创建一个父工程 tlias-parent ,然后让上述的三个模块 tlias-pojo、tlias-utils、tlias-web-management 都来继承这个父工程 。 然后再将各个模块中都共有的依赖,都提取到父工程 tlias-parent 中进行配置,只要子工程继承了父工程,依赖它也会继承下来,这样就无需在各个子工程中进行配置了。

- 概念:继承描述的是两个工程间的关系,与 java 中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。
- 作用:简化依赖配置、统一管理依赖
- 实现:
1 | <parent> |
这是我们在这里先介绍一下什么是继承以及继承的作用,以及在 maven 当中如何来实现这层继承关系。接下来我们就来创建这样一个 parent 父工程,我们就可以将各个子工程当中共有的这部分依赖统一的定义在父工程 parent 当中,从而来简化子工程的依赖配置。接下来我们来看一下具体的操作步骤。
我们在这里先介绍一下什么是继承以及继承的作用,以及在 maven 当中如何来实现这层继承关系。接下来我们就来创建这样一个 parent 父工程,我们就可以将各个子工程当中共有的这部分依赖,统一的定义在父工程 parent 当中,从而来简化子工程的依赖配置。
继承关系
思路分析
我们当前的项目 tlias-web-management,还稍微有一点特殊,因为是一个 springboot 项目,而所有的 springboot 项目都有一个统一的父工程,就是 spring-boot-starter-parent。 与 java 语言类似,Maven 不支持多继承,一个 maven 项目只能继承一个父工程,如果继承了 spring-boot-starter-parent,就没法继承我们自己定义的父工程 tlias-parent 了。
那我们怎么来解决这个问题呢?
那此时,大家可以想一下,Java 虽然不支持多继承,但是可以支持多重继承,比如:A 继承 B, B 继承 C。 那在 Maven 中也是支持多重继承的,所以呢,我们就可以让 我们自己创建的三个模块,都继承 tlias-parent,而 tlias-parent 再继承 spring-boot-starter-parent,就可以了。 具体结构如下:



实现
1). 创建 maven 模块 tlias-parent ,该工程为父工程,设置打包方式 pom(默认 jar)。

工程结构如下(src 目录不需要,直接删除):


relativePath自闭合代表将会从本地仓库查找依赖,本地仓库没有会从中央仓库查找
父工程 tlias-parent 的 pom.xml 文件配置如下:
1 | _<?_xml version="1.0" encoding="UTF-8"_?>_ |
[!TIP]
Maven 打包方式:
- jar:普通模块打包,springboot 项目基本都是 jar 包(内嵌 tomcat 运行)
- war:普通 web 程序打包,需要部署在外部的 tomcat 服务器中运行
- pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
2). 在子工程(tlias-pojo、tlias-utils、tlias-web-management)的 pom.xml 文件中,配置继承关系。
1 | # 配置父工程的坐标 |
这里是以 tlias-utils 为例,指定了其父工程。其他的模块,都是相同的配置方式。
[!TIP]
注意:
- 在子工程中,配置了继承关系之后,坐标中的 groupId 是可以省略的,因为会自动继承父工程的 。
- relativePath 指定父工程的 pom 文件的相对位置(如果不指定,将从本地仓库/远程仓库查找该工程)。
- …/ 代表的上一级目录
3). 在父工程中配置各个工程共有的依赖(子工程会自动继承父工程的依赖)。
1 | _<?_xml version="1.0" encoding="UTF-8"_?>_ |
此时,我们已经将各个子工程中共有的依赖(lombok 和 spring-boot-starter),都定义在了父工程中,子工程中的这一项依赖,就可以直接删除了。删除之后,我们会看到父工程中配置的依赖 lombok,子工程直接继承下来了。

[!TIP]
工程结构说明:
- 我们当前的项目结构为:
tlias-parenttlias-pojotlias-utilstlias-web-management
因为我们是项目开发完毕之后,给大家基于现有项目拆分的各个模块,tlias-web-management已经存在了,然后再创建各个模块与父工程,所以父工程与模块之间是平级的。
[!TIP]
工程结构说明:
- 而实际项目中,可能还会见到下面的工程结构:
tlias-parenttlias-pojotlias-utilstlias-web-management
而在真实的企业开发中,都是先设计好模块之后,再开始创建模块,开发项目。 那此时呢,一般都会先创建父工程 tlias-parent,然后将创建的各个子模块,都放在父工程 parent 下面。 这样层级结构会更加清晰一些。
PS:上面两种工程结构,都是可以正常使用的,没有一点问题。 只不过,第二种结构,看起来,父子工程结构更加清晰、更加直观。

版本锁定
场景
如果项目中各个模块中都公共的这部分依赖,我们可以直接定义在父工程中,从而简化子工程的配置。 然而在项目开发中,还有一部分依赖,并不是各个模块都共有的,可能只是其中的一小部分模块中使用到了这个依赖。
比如:在 tlias-web-management、tlias-web-system、tlias-web-report 这三个子工程中,都使用到了 jwt 的依赖。 但是 tlias-pojo、tlias-utils 中并不需要这个依赖,那此时,这个依赖,我们不会直接配置在父工程 tlias-parent 中,而是哪个模块需要,就在哪个模块中配置。
而由于是一个项目中的多个模块,那多个模块中,我们要使用的同一个依赖的版本要一致,这样便于项目依赖的统一管理。比如:这个 jwt 依赖,我们都使用的是 0.9.1 这个版本。

那假如说,我们项目要升级,要使用到 jwt 最新版本 0.9.2 中的一个新功能,那此时需要将依赖的版本升级到 0.9.2,那此时该怎么做呢 ?
第一步:去找当前项目中所有的模块的 pom.xml 配置文件,看哪些模块用到了 jwt 的依赖。
第二步:找到这个依赖之后,将其版本 version,更换为 0.9.2。
问题:如果项目拆分的模块比较多,每一次更换版本,我们都得找到这个项目中的每一个模块,一个一个的更改。 很容易就会出现,遗漏掉一个模块,忘记更换版本的情况。
那我们又该如何来解决这个问题,如何来统一管理各个依赖的版本呢?
答案:Maven 的版本锁定功能。

介绍
在 maven 中,可以在父工程的 pom 文件中通过 <dependencyManagement> 来统一管理依赖版本。
- 父工程:
1 | <!--统一管理依赖版本--> |
- 子工程:
1 | <dependencies> |
[!TIP]
注意:
- 在父工程中所配置的
<dependencyManagement>只能统一管理依赖版本,并不会将这个依赖直接引入进来。 这点和<dependencies>(直接引入对应的依赖) 是不同的。 - 子工程要使用这个依赖,还是需要引入的,只是此时就无需指定
<version>版本号了,父工程统一管理。变更依赖版本,只需在父工程中统一变更。
实现
接下来,我们就可以将 tlias-utils 模块中单独配置的依赖,将其版本统一交给 tlias-parent 进行统一管理。
具体步骤如下:
1). tlias-parent中的配置
1 | <dependencyManagement> |
2). tlias-utils 中的 pom.xml 配置
如果依赖的版本已经在父工程进行了统一管理,所以在子工程中就无需再配置依赖的版本了。
1 | <dependencies> |
[!TIP]
我们之所以,在 springboot 项目中很多时候,引入依赖坐标,都不需要指定依赖的版本<version>,是因为在父工程 spring-boot-starter-parent 中已经通过<dependencyManagement>对依赖的版本进行了统一的管理维护。
属性配置
我们也可以通过自定义属性及属性引用的形式,在父工程中将依赖的版本号进行集中管理维护。

具体语法为:
1). 自定义属性(pom.xml文件中的properties)
1 | <properties> |
2). 引用属性
1 | <dependency> |
接下来,我们就可以在父工程中,将所有的版本号,都集中管理维护起来。
1 | _<?_xml version="1.0" encoding="UTF-8"_?>_ |
版本集中管理之后,我们要想修改依赖的版本,就只需要在父工程中自定义属性的位置,修改对应的属性值即可。
[!TIP]
面试题:dependencyManagement> 与的区别是什么?
<dependencies>是直接依赖,在父工程配置了依赖,子工程会直接继承下来。<dependencyManagement>是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
spring-boot-starter-xxx也是利用版本锁定和自定义属性的方式进行了统一的管理, 这里以spring-boot-starter-web版本为例:父工程 spring-boot-starter-parent 的父工程 spring-boot-dependencies打包方式为pom, 将所有的依赖版本定义在properties, 对于项目按需取依赖的原则,将spring-boot-starter-web 通过
的方式统一管理依赖的版本(官方控制web版本使用的方式),不会直接依赖,在子工程用到的时候只需要指定groupId和artifactid即可不用指定版本,
聚合(针对项目打包模块间所需依赖设计)
分模块设计与开发之后啊,我们的项目被拆分为多个模块,而模块之间的关系,可能错综复杂。 那就比如我们当前的案例项目,结构如下(相对还是比较简单的):

此时,tlias-web-management 模块的父工程是 tlias-parent,该模块又依赖了 tlias-pojo、tlias-utils 模块。 那此时,我们要想将 tlias-web-management 模块打包,是比较繁琐的。因为在进行项目打包时,maven 会从本地仓库中来查找 tlias-parent 父工程,以及它所依赖的模块 tlias-pojo、tlias-utils,而本地仓库目前是没有这几个依赖的。
所以,我们再打包 tlias-web-management 模块前,需要将 tlias-parent、tlias-pojo、tlias-utils 分别执行 install 生命周期安装到 maven 的本地仓库,然后再针对于 tlias-web-management 模块执行 package 进行打包操作。
那此时,大家试想一下,如果开发一个大型项目,拆分的模块很多,模块之间的依赖关系错综复杂,那此时要进行项目的打包、安装操作,是非常繁琐的。 而我们接下来,要讲解的 maven 的聚合就是来解决这个问题的,通过 maven 的聚合就可以轻松实现项目的一键构建(清理、编译、测试、打包、安装等)。
介绍

- **聚合:**将多个模块组织成一个整体,同时进行项目的构建。
- **聚合工程:**一个不具有业务功能的“空”工程(有且仅有一个 pom 文件) 【PS:一般来说,继承关系中的父工程与聚合关系中的聚合工程是同一个】
- **作用:**快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)

实现
在 maven 中,我们可以在聚合工程中通过 <moudules> 设置当前聚合工程所包含的子模块的名称。我们可以在 tlias-parent 中,添加如下配置,来指定当前聚合工程,需要聚合的模块:
1 | <!-- 聚合其他模块 --> |
那此时,我们要进行编译、打包、安装操作,就无需在每一个模块上操作了。只需要在聚合工程上,统一进行操作就可以了。
**测试:**执行在聚合工程 tlias-parent 中执行 package 打包指令

那 tlias-parent 中所聚合的其他模块全部都会执行 package 指令,这就是通过聚合实现项目的一键构建(一键清理 clean、一键编译 compile、一键测试 test、一键打包 package、一键安装 install 等)。
继承与聚合对比
-
作用
- 聚合用于快速构建项目
- 继承用于简化依赖配置、统一管理依赖
-
相同点:
- 聚合与继承的 pom.xml 文件打包方式均为 pom,通常将两种关系制作到同一个 pom 文件中
- 聚合与继承均属于设计型模块,并无实际的模块内容
-
不同点:
- 聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
- 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

私服
前面我们在讲解多模块开发的时候,我们讲到我们所拆分的模块是可以在同一个公司各个项目组之间进行资源共享的。这个模块的资源共享,就需要通过我们接下来所讲解的 Maven 的私服来实现。
首先我们先介绍一下什么是私服,以及它的作用是什么。再来介绍一下我们如何将每位模块打包上传到私服,以及从私服当中来下载。
场景
在介绍什么是私服之前,我们先来分析一下同一个公司,两个项目组之间如何基于私服进行资源的共享。
假设现在有两个团队,A 和 B。 A 开发了一个模块 tlias-utils,模块开发完毕之后,将模块打成 jar 包,并安装到了 A 的本地仓库。

那此时,该公司的 B 团队开发项目时,要想使用 tlias-utils 中提供的工具类,该怎么办呢? 对于 maven 项目来说,是不是在 pom.xml 文件中引入 tlias-utils 的坐标就可以了呢?

大家可以思考一下,当 B 团队在 maven 项目的 pom.xml 配置文件中引入了依赖的坐标之后,maven 是如何查找这个依赖的? 查找顺序为:
1). 本地仓库:本地仓库中是没有这个依赖 jar 包的。
2). 远程中央仓库:由于该模块时自己公司开发的,远程仓库中也没有这个依赖。
因为目前 tlias-utils 这个依赖,还在 A 的本地仓库中的。 B 电脑上的 maven 项目,是不可能找得到 A 电脑上 maven 本地仓库的 jar 包的。 那此时,大家可能会有一个想法:因为 A 和 B 都会连接中央仓库,我们可以将 A 本地仓库的 jar 包,直接上传到中央仓库,然后 B 从中央仓库中下载 tlias-utils 这个依赖。

这个想法很美好,但是现实很残酷。这个方案是行不通的,因为中央仓库全球只有一个,不是什么人都可以往中央仓库中来上传 jar 包的,我们是没有权限操作的。
那此时,maven 的私服就出场了,私服其实就是架设在公司局域网内部的一台服务器,就是一种特殊的远程仓库。
有了私服之后,各个团队就可以直接来连接私服了。 A 连接上私服之后,他就可以把 jar 包直接上传到私服当中。我公司自己内部搭建的服务器,我是不是有权限操作呀,把 jar 包上传到私服之后,我让 B 团队的所有开发人员也连接同一台私服。连接上这一台私服之后,他就会根据坐标的信息,直接从私服当中将对应的 jar 包下载到自己的本地仓库,这样就可以使用到依赖当中所提供的一些工具类了。这样我们就可以通过私服来完成资源的共享。

而如果我们在项目中需要使用其他第三方提供的依赖,如果本地仓库没有,也会自动连接私服下载,如果私服没有,私服此时会自动连接中央仓库,去中央仓库中下载依赖,然后将下载的依赖存储在私服仓库及本地仓库中。

介绍
-
**私服:**是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题。
-
依赖查找顺序:
- 本地仓库
- 私服仓库
- 中央仓库
-
**注意事项:**私服在企业项目开发中,一个项目/公司,只需要一台即可(无需我们自己搭建,会使用即可)。

资源上传与下载
步骤分析

资源上传与下载,我们需要做三步配置,执行一条指令。
- 第一步配置:在 maven 的配置文件中配置访问私服的用户名、密码。
- 第二步配置:在 maven 的配置文件中配置连接私服的地址(url 地址)。
- 第三步配置:在项目的 pom.xml 文件中配置上传资源的位置(url 地址)。
- 配置好了上述三步之后,要上传资源到私服仓库,就执行执行 maven 生命周期:
deploy。
[!TIP]
私服仓库说明:
- RELEASE:存储自己开发的 RELEASE 发布版本的资源。
- SNAPSHOT:存储自己开发的 SNAPSHOT 发布版本的资源。
- Central:存储的是从中央仓库下载下来的依赖。
[!TIP]
项目版本说明:
- RELEASE(发布版本):功能趋于稳定、当前更新停止,可以用于发行的版本,存储在私服中的 RELEASE 仓库中。
- SNAPSHOT(快照版本):功能不稳定、尚处于开发中的版本,即快照版本,存储在私服的 SNAPSHOT 仓库中。

具体操作
私服已经搭建好了,已经启动起来了,我们可以访问私服测试:http://localhost:8081。

私服准备好了之后,我们要做如下几步配置:
1. 设置私服的访问用户名/密码(在自己 maven 安装目录下的conf/settings.xml中的 servers 中配置)
1 | <server> |
2. 设置私服依赖下载的仓库组地址(在自己 maven 安装目录下的conf/settings.xml中的 mirrors 中配置)
1 | <mirror> |
3.设置私服依赖下载的仓库组地址(在自己 maven 安装目录下的conf/settings.xml中的 profiles 中配置)[不加入这段配置一般snapshots中的依赖不能使用]
1 | <profile> |
4. IDEA 的 maven 工程的 pom 文件中配置上传(发布)地址(直接在tlias-parent中配置发布地址)
1 | <distributionManagement> |
配置完成之后,我们就可以在 tlias-parent 中执行 deploy 生命周期,将项目发布到私服仓库中。

通过日志,我们可以看到,这几个模块打的 jar 包确实已经上传到了私服仓库中(由于当前我们的项目是 SNAPSHOT 版本,所以 jar 包是上传到了 snapshot 仓库中)。
那接下来,我们再来打开私服来看一下:

我们看到,我们项目中的这几个模块,在私服中都有了。 那接下来,当其他项目组的开发人员在项目中,就可以直接通过依赖的坐标,就可以完成引入对应的依赖,此时本地仓库没有,就会自动从私服仓库中下载。
[!TIP]
备注说明:在真实的企业开发中,私服都是在远程服务器中的,并不是在本地的。这里我们只是为了方便演示。






