Maven学习笔记

学习来源:黑马教程

学习时间:2022年1月21日

1 Maven简介

1.1 传统项目管理状态分析

  • jar包不统一,不兼容
  • 工程升级维护过程操作繁琐

image-20220121135112719

1.2 Maven是什么

  • maven的本质是一个项目管理工具,将项目开发和管理过程抽象成一个项目对象模型(POM)
  • POM(Project Object Model):项目对象模型

image-20220121135910882

1.3 Maven的作用

  • 项目构建:提供标准的、跨平台的自动化项目构建方式
  • 依赖管理:方便快捷的管理项目依赖的资源(jar包),避免资源间的版本冲突问题
  • 统一的开发结构:提供标准的、统一的项目结构

统一的开发结构

2 下载与安装

  1. 将下载好的文件解压缩,我的是放在C:\Program Files\apache-maven-3.6.3

image-20220121141059406

  1. 配置环境变量:

image-20220121141123867

image-20220121141235976

  1. 在cmd查看是否配置成功:
1
mvn -v

image-20220121141338312

3 Maven基础概念

3.1 仓库

  • 仓库:用于存储资源,包含各种jar包

image-20220121141827509

仓库分类

  • 本地仓库:自己电脑上存储资源的仓库,连接远程仓库获取资源
  • 远程仓库:包括私服和中央仓库,即非本机电脑上的仓库,为本地仓库提供资源
    • 中央仓库:Maven团队维护,存储所有资源的仓库
    • 私服:部门或公司范围内存储资源的仓库,它从中央仓库来获取资源

私服的作用

  • 保存具有版本的资源,包含购买或自主研发的jar
    • 中央仓库的jar都是开源的,不能存储具有版权的资源
  • 一定范围内共享资源,仅对内部开放,不对外共享

3.2 坐标

  • 坐标:Maven中的坐标用于描述仓库中资源的位置

  • 主要组成:

    • groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如org.mybatis
    • artifactId:定义当前Maven项目名称(通常是模块名称,例如CRM,SMS)
    • version:定义当前项目版本号
    • packaging:定义该项目的打包方式

示例:

1
2
3
4
5
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.1</version>
</dependency>
  • 作用:使用唯一标识,唯一性地定位资源位置,通过该标识可以将资源的识别与下载工作交由机器完成

3.3 仓库配置

3.3.1 本地仓库配置

Maven启动后,会自动保存下载的资源到本地仓库,仓库位置可在conf/settings.xml下查看

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

其中的默认位置就是:

1
<localRepository>${user.home}/.m2/repository</localRepository>

image-20220121150420130

可以自定义本地仓库的位置,将上述代码修改为:

1
<localRepository>D:/DevEnvironment/localRepository</localRepository>

3.3.2 远程仓库配置

Maven默认连接的远程仓库的位置:

1
2
3
4
5
6
7
8
9
10
11
<repositories> 
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

可以将其替换为镜像仓库,修改conf/settings.xml,配置为阿里云的镜像仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
<mirrors> 
<!-- 配置具体的仓库的下载镜像 -->
<mirror>
<!-- 此镜像的唯一标识符,用来区分不同的镜像 -->
<id>nexus-aliyun</id>
<!-- 对哪种仓库进行镜像,即对哪个远程仓库进行替换,此处为中央仓库 -->
<mirrorOf>central</mirrorOf>
<!-- 镜像名称 -->
<name>Nexus aliyun</name>
<!-- 镜像的URL -->
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>

4 手工制作Maven项目

4.1 项目结构搭建

Maven工程目录结构

image-20220121152837716

代码

src/main/java/com/hongyi/下新建Demo.java

1
2
3
4
5
6
7
package com.hongyi;
public class Demo{
public String say(String name) {
System.out.println("Hello, " + name);
return "Hello, " + name;
}
}

src/test/java/com/hongyi/下新建DemoTest.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.hongyi;
import org.junit.Test;
import org.junit.Assert;

public class DemoTest {
@Test
public void testSay() {
Demo d = new Demo();
String ret = d.say("Maven");
Assert.assertEquals("Hello, Maven", ret);
}
}

在项目根目录(和src同级)下新建一个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
<?xml version="1.0" encoding="UTF-8"?>
<!-- pom文件所需的xml头,固定 -->
<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>
<!-- 组织名称(域名反写) -->
<groupId>com.hongyi</groupId>
<!-- 项目名称 -->
<artifactId>project-maven</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<!-- 依赖的资源 -->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

<!-- 设定字符集和maven编译java程序时的jdk版本,这里是jdk11 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>

4.2 项目构建

Maven常用构建命令:使用mvn开头,后面添加功能参数,可以一次执行多个命令,使用空格分隔

命令 作用
mvn compile 编译,生成target文件
mvn clean 清理target文件
mvn test 测试
mvn package 打包,包含编译和测试,生成jar包
mvn install 将jar包安装到本地仓库

使用示例

  1. 进入到项目根目录,运行编译命令
1
mvn compile

image-20220121154226132

如果jdk版本指定过低,会报以下错误:

image-20220121154303341

  1. 此时查看本地仓库,会有下载好的资源

image-20220121154416911

  1. 此时,在项目根目录中也会有编译完成的项目target

image-20220121154511409

  1. 执行clean命令,会清除target目录
1
mvn clean
  1. 执行test命令,也会编译生成target,并且附带有测试结果文件
1
mvn test

image-20220121154719117

测试报告:

image-20220121154837733

  1. 执行package命令,会将源程序编译并打包
1
mvn package

image-20220121155025903

注意:打包命令包含了编译和测试两个命令。

  1. 执行install命令,会将该项目安装到本地仓库
1
mvn install

查看本地仓库,按照项目的groupId查找,例如com.hongyi

image-20220121155420854

其中项目名称artifactId又决定了一层目录,这里为project-maven

image-20220121155551150

版本号version又决定了一层目录:

image-20220121155645856

最后的文件内容,即为打好包的jar包和对应的pom文件:

image-20220121155718843

5 IDEA生成Maven项目

5.1 创建空Maven项目

  1. 新建一个空的工程,然后选择jdk版本
  2. 打开setting,搜索maven并进行相关配置:

image-20220121160753851

  1. 新建模块module

image-20220121160928095

  1. 然后标记各个目录的功能:

image-20220121161144859

  1. 在idea右侧有maven相关的操作:

image-20220121161727541

与mvn的各种命令相同,不再赘述。

  1. 按照上一节的操作,添加各个类,最后的项目结构如下:

image-20220121161833106

5.2 使用骨架创建Maven项目

  1. 新建一个模块。选择骨架创建maven项目,这里选择quick-start

image-20220121164039937

  1. 项目结构如下:

image-20220121164402283

  1. 按照maven项目的经典结构进行改造,改造后的结果如下:

image-20220121164437724

6 依赖管理

6.0 project和module的关系

在 IntelliJ IDEA 中Project是最顶级的结构单元,然后就是Module,一个Project可以有多个Module。目前,主流的大型项目结构基本都是多Module的结构,这类项目一般是按功能划分的,比如:user-core-moduleuser-facade-moduleuser-hessian-module等等,模块之间彼此可以相互依赖。通过这些Module的命名可以看出,它们都是处于同一个项目中的模块,彼此之间是有着不可分割的业务关系。因此,我们可以大致总结出:一个Project是由一个或多个Module组成

  • 当为单Module项目的时候,这个单独的Module实际上就是一个Project
  • 当为多Module项目的时候,多个模块处于同一个Project之中,此时彼此之间具有互相依赖的关联关系。

  • 创建一个空的项目:

image-20230614201653431

简单地理解Project就是一个单纯的目录,只是这个目录在命名上必须有其代表性的意义。在缺省情况下,IntelliJ IDEA 是默认单Project单Module的,这时Project和Module合二为一。

image-20230614201711968

.idea文件夹在那个文件夹,则此文件夹为Project文件夹

  • 创建一个模块

image-20230614201915423

image-20230614202012536

  • 再次创建一个模块

image-20230614202057171

image-20230614202110307

  • 最终结果

image-20230614202122659

6.1 项目搭建

  1. 创建三个maven模块,project1project2project3,分别添加log4j的依赖,版本号依次为2.13.02.13.12.13.2,项目结构如下:

image-20220121170201713

  1. 各个模块的maven依赖如下:

image-20220121170234318

6.2 依赖配置

  • 依赖:指当前项目运行所需的jar,一个项目可以设置多个依赖
  • 格式:
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 设置当前项目所依赖的所有jar --> 
<dependencies>
<!-- 设置具体的依赖 -->
<dependency>
<!-- 依赖所属群组id -->
<groupId>junit</groupId>
<!-- 依赖所属项目id -->
<artifactId>junit</artifactId>
<!-- 版本号 -->
<version>4.12</version>
</dependency>
</dependencies>

6.3 依赖传递

6.3.1 概念

  • 直接依赖:在当前项目中通过依赖配置建立的依赖关系
  • 间接依赖:被依赖的资源如果依赖其他资源,则当前项目间接依赖其他资源

image-20220121170443225

6.3.2 依赖传递的冲突问题

  • 路径优先:当依赖中出现相同的资源,层级越深,优先级越低,反之越高
  • 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖靠后的
  • 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的

image-20220121171540596

示例

  1. project2依赖project3
1
2
3
4
5
6
<!-- project2的pom -->
<dependency>
<groupId>com.hongyi</groupId>
<artifactId>project3</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
  1. project3中添加junit的依赖
1
2
3
4
5
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
  1. 查看此时的依赖关系

image-20220121171129186

对当前项目project2而言,project3log4j:2.13.1为1度依赖,而project3log4j:2.13.2为2度依赖,因此被忽略(omitted

6.3.3 可选依赖

可选依赖是指对外隐藏当前所依赖的资源,即不透明性。配置是在被依赖方

1
2
3
4
5
6
7
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- 添加这一选项,并设为true -->
<optional>true</optional>
</dependency>

image-20220121172029873

发现该依赖被隐藏了。

6.3.4 排除依赖

排除依赖是主动断开依赖的资源,被排除的资源不需要指定版本。配置是在依赖方

例如project2要排除掉project3中的依赖log4j:2.13.2(虽然根据依赖冲突的解决也没有生效)。在project2的pom文件中添加:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 要排除掉project3中的log4j依赖 -->
<dependency>
<groupId>com.hongyi</groupId>
<artifactId>project3</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</exclusion>
</exclusions>
</dependency>

执行结果:

image-20220121180142958

发现project2中没有了log4j:2.13.2的依赖。只剩下junit的依赖

6.4 依赖范围

6.4.1 概述

依赖的jar默认情况可以在任何地方使用,可以通过scope标签设定其作用范围

  • 作用范围
    • 主程序范围有效(main文件范围内)
    • 测试程序范围有效(test文件范围内)
    • 是否参与打包(package指令范围内)
scope 主代码 测试代码 打包 示例
compile(默认) log4j
test junit
provided servlet-api
runtime jdbc

示例

例如在project2中新增依赖:

1
2
3
4
5
6
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
<scope>test</scope>
</dependency>

执行结果:

image-20220121181457657

6.4.2 依赖范围传递性

带有依赖范围的资源在进行传递时,作用范围将受到影响。

image-20220121181641821

7 生命周期与插件

7.1 项目构建生命周期

Maven构建生命周期描述的是一次构建过程经历了多少个事件

default的生命周期

Maven对项目构建的生命周期划分为3套:

  • clean:清理工作
  • default:核心工作,例如编译,测试,打包,部署等
  • site:产生报告,发布站点

clean生命周期

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

default构建生命周期

image-20220122133255620

说明:图中标红的是Maven典型(常用)的构建生命周期点。

例如当执行mvn compile命令时,会从头(mvn validate)开始执行命令至mvn compile初。

又如执行mvn package命令时,也会从头开始执行,直到mvn package命令处停止。

site构建生命周期

image-20220122133316989

image-20220122135358105

7.2 插件

  • 插件与生命周期内的阶段绑定,在执行到对应生命周期时执行对应的插件功能
  • 默认maven在各个生命周期上绑定有预设的功能
  • 通过插件可以自定义其他功能

在官网中可以查阅到各种插件:

image-20220122133736137

理解:生命周期相当于人活到了几岁,而插件相当于在几岁时需要做哪些事情。

演示示例——自定义插件

  1. 项目搭建:在project3中的主类和测试类中分别创建Demo.javaDemoTest.java
1
2
3
4
5
public class Demo {
public void run() {
System.out.println("source code plugin...");
}
}
1
2
3
4
5
public class DemoTest {
public void test() {
System.out.println("test source code plugin...");
}
}
  1. 自定义插件写在<bulid>标签中。在project3的pom文件中写入:
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
<build>
<plugins>
<plugin>
<!-- 自定义插件所需的坐标 -->
<!-- 这个插件的功能是生成项目的java源码 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<!-- 定义在哪个生命周期点执行,可定义多个生命周期点execution -->
<executions>
<execution>
<!-- 定义在什么范围类使得插件生效,可定义多个范围goal -->
<goals>
<!-- 定义在什么范围类使得插件生效 -->
<!-- jar表示在main中生效 -->
<!-- test-jar表示在test中生效 -->
<goal>jar</goal>
</goals>
<!-- 在哪个生命周期点执行,此处为generate-test-resources -->
<phase>generate-test-resources</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>

image-20220122140049804

此处演示在generate-test-resources处自定义了一个插件,该插件生成项目的源码文件。意味着,例如执行mvn test时,会执行到该插件。执行mvn compile时,不会执行到该插件。而且该插件只会生成main中的源代码。

  1. 刷新maven后,可看到该插件:

image-20220122140449012

  1. 执行mvn compile,发现target并没有生成源码文件

image-20220122140607193

  1. 执行mvn test,发现生成了源码文件

image-20220122140707370

打开文件,发现是main的源码:

image-20220122140806790

  1. 添加一个goaltest-jar。首先执行clean,再执行package

image-20220122140944862

发现有打包好的工程文件、main源码和test源码。

image-20220122141025450

7.3 spring-boot-maven-plugin

7.3.1 简介

spring-boot-maven-plugin 是一个 Maven 插件,专门为 Spring Boot 项目设计,用于简化 Spring Boot 应用程序的构建和部署。

  • 功能

    • 重新打包:使用 repackage 目标将 Spring Boot 应用重新打包为一个可执行 JAR 或 WAR 文件,包含所有依赖项,使得应用可以直接运行。
    • 运行应用:使用 spring-boot:run 目标在开发过程中直接运行 Spring Boot 应用,简化开发和测试流程。
    • 构建镜像:支持将 Spring Boot 应用打包为 Docker 镜像,通过 spring-boot:build-image 目标简化 Docker 镜像构建过程。
    • 生成应用特定的构建信息:在构建过程中自动生成 build-info.properties 文件,包含版本、时间戳等构建信息。
  • 引入:

1
2
3
4
5
6
7
8
9
10
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!-- 打包后的名字 -->
<finalName>app</finalName>
</build>

刷新maven:

image-20240805163218158

7.3.2 普通方式打包

现在删除上面的配置,刷新maven,再点击package

image-20240805163456499

发现jar包的大小只有几kb,且运行后报错:

image-20240805163612641

原因:一般的maven项目的打包命令,不会把依赖的jar包也打包进去的,只是会放在jar包的同目录下,能够引用就可以了。

7.3.3 插件打包

  • 添加插件配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>app</finalName>
</build>
  • 打包后,能够正常运行

image-20240805164226489

原因:spring-boot-maven-plugin插件,会将依赖的jar包全部打包进去。该文件包含了所有的依赖和资源文件,可以直接在命令行或者传统的 Java Web 服务器上启动运行。


  • 排除provided

默认情况下,repackage命令所生成的包,会把项目中所有的依赖都打进去。但其实在项目中scope为provided的依赖,比如 lombok、mybatis-plus等,只作用于编译阶段,编译完成就没用了。若除去provided依赖,可以使用如下的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
<finalName>app</finalName>
</build>

7.3.4 JAR包结构

在 Spring Boot 应用的可执行 JAR 文件中,通常包含以下几个关键目录:BOOT-INFMETA-INForg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
my-spring-boot-app.jar/
├── BOOT-INF/
│ ├── classes/
│ │ ├── com/
│ │ │ └── example/
│ │ │ └── MyApplication.class
│ │ └── application.properties
│ └── lib/
│ ├── spring-boot-starter-2.5.5.jar
│ └── other-dependencies.jar
├── META-INF/
│ ├── MANIFEST.MF
│ └── other-metadata-files
└── org/
└── springframework/
└── boot/
└── autoconfigure/
└── ...

image-20240805164905980

① BOOT-INF
1
2
3
4
5
6
7
8
9
BOOT-INF/
├── classes/
│ ├── com/
│ │ └── example/
│ │ └── MyApplication.class
│ └── application.properties
└── lib/
├── spring-boot-starter-1.5.9.RELEASE.jar
└── other-dependencies.jar

BOOT-INF 目录是 Spring Boot 特有的目录结构,用于包含应用的主要内容和依赖项。这个目录通常包含两个子目录:

  • BOOT-INF/classes:包含编译后的应用程序类文件和资源文件。这是你编写的 Java 类文件被放置的地方。
  • BOOT-INF/lib:包含所有应用程序的依赖 JAR 文件。这些依赖项会在运行时被包含在类路径中。
② META-INF
1
2
3
META-INF/
├── MANIFEST.MF
└── other-metadata-files

META-INF 目录包含 JAR 文件的元数据和配置信息。这个目录通常包含一些标准文件和目录:

  • MANIFEST.MF:包含 JAR 文件的清单信息,包括版本、主类等元数据。
  • 其他可能的文件和目录:如服务提供者配置文件、持久性配置文件等。

MANIFEST.MF 文件的内容示例:

1
2
3
4
Manifest-Version: 1.0
Main-Class: com.example.MyApplication
Start-Class: com.example.MyApplication
Spring-Boot-Version: 2.5.5
③ org
1
2
3
4
5
org/
└── springframework/
└── boot/
└── autoconfigure/
└── ...

org 目录通常包含 Spring Boot 自身和其他依赖库的类文件。这个目录会在解压后的依赖 JAR 文件中看到,它不是 Spring Boot 应用特有的,但常见于依赖库的包名。

综上,这些目录和文件共同构成了一个可执行的 Spring Boot JAR 文件,使得应用能够独立运行而不依赖于外部环境。

8 分模块开发与设计

8.1 目标

目标:将ssm项目拆分。

image-20220122151324949

如上图所示,将ssm项目拆分成右侧几个模块。

8.2 ssm-pojo拆分

  1. 新建一个maven项目模块

image-20220122151519992

  1. 拷贝原始项目中对应的相关代码到该模块中,删除不必要的测试类和配置文件

image-20220122151754885

这个模块的pom没有依赖任何资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>ssm_pojo</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

</project>

8.3 ssm_dao拆分

  1. 新建模块ssm_dao,拷贝原始项目中对应的相关代码到该模块中,删除不必要的测试类和非dao层的配置文件。

image-20220122153355243

  1. 对于dao层的pom文件内容如下(即所需的资源):注意ssm_pojo资源的导入
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
<dependencies>
<!--导入资源文件pojo-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--spring环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--mybatis环境-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<!--mysql环境-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--spring整合jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--spring整合mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--分页插件坐标-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
</dependencies>
  1. 运行mvn compile发现构建失败:

image-20220122153626040

原因在于,导入了ssm_pojo的资源坐标,但是在本地仓库中没有这个资源,因此需要对ssm_pojo项目进行mvn install,将其安装到本地仓库:

image-20220122153759349

然后再对ssm_dao进行编译,通过。

image-20220122153839665

  1. 对dao的配置文件applicationContext.xml进行修改,删去非dao层的代码:
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启bean注解扫描-->
<context:component-scan base-package="com.itheima"/>
<!--加载properties文件-->
<context:property-placeholder location="classpath*:jdbc.properties"/>
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--整合mybatis到spring中-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.itheima.domain"/>
<!--分页插件-->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>
<!--映射扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.dao"/>
</bean>
</beans>

8.4 ssm_service拆分

  1. 新建模块ssm_service,拷贝原始项目中对应的相关代码到该模块中,删除不必要的非service层的配置文件。注意保留测试类test。

image-20220122160937202

  1. 对于service层的pom文件内容如下(即所需的资源):注意ssm_dao资源的导入,此外无需导入ssm_pojo的资源,因为他间接依赖了ssm_dao的ssm_pojo资源。对ssm_dao进行install命令。
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
<dependencies>
<!--导入ssm_dao的资源-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_dao</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--spring环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>
  1. 对ssm_service的配置文件applicationContext.xml进行修改,删去非service层的代码,此外将其修改为applicationContext-service.xml。同理将ssm_dao的配置文件名修改为applicationContext-dao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--开启bean注解扫描-->
<context:component-scan base-package="com.itheima"/>

<!--开启注解式事务-->
<tx:annotation-driven transaction-manager="txManager"/>

<!--事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

</beans>
  1. 修改单元测试引入的配置文件名,由单个文件修改为多个文件:
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
@RunWith(SpringJUnit4ClassRunner.class)
// 修改这里:要引入dao层的配置文件
@ContextConfiguration(locations = {"classpath:applicationContext-service.xml", "classpath:applicationContext-dao.xml"})
public class UserServiceTest {

@Autowired
private UserService userService;

@Test
public void testSave(){
User user = new User();
user.setUserName("Jock");
user.setPassword("root");
user.setRealName("Jockme");
user.setGender(1);
user.setBirthday(new Date(333333000000L));

userService.save(user);
}

@Test
public void testDelete(){
User user = new User();
userService.delete(2);
}

@Test
public void testUpdate(){
User user = new User();
user.setUuid(1);
user.setUserName("Jockme");
user.setPassword("root");
user.setRealName("JockIsMe");
user.setGender(1);
user.setBirthday(new Date(333333000000L));

userService.update(user);
}

@Test
public void testGet(){
User user = userService.get(1);
System.out.println(user);
}

@Test
public void testGetAll(){
PageInfo<User> all = userService.getAll(2, 2);
System.out.println(all);
System.out.println(all.getList().get(0));
System.out.println(all.getList().get(1));
}

@Test
public void testLogin(){
User user = userService.login("Jockme", "root");
System.out.println(user);
}
}
  1. 运行mvn test进行用例测试,通过。最后执行mvn install,将service层代码安装到本地仓库中。

image-20220122164503334

8.5 ssm_controller拆分

  1. 利用骨架webapp新建maven模块ssm_controller。拷贝原始项目中对应的相关代码到该模块中,删除不必要的非controller层的配置文件。

image-20220122164047807

  1. 对于controller层的pom文件内容如下(即所需的资源):注意ssm_service资源的导入和间接依赖:
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
<dependencies>
<!--导入ssm_service的资源-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!--springmvc环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--jackson相关坐标3个-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!--servlet环境-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<!--设置插件-->
<plugins>
<!--具体的插件配置-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
  1. 修改web.xml中的加载spring环境的配置文件(dao层和service层),使用*进行通配
1
2
3
4
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext-*.xml</param-value>
</context-param>
  1. 配置tomcat服务器并使用postman测试:http://localhost:8080/user?userName=Mark&password=123456&realName=Hongyi&gender=1&birthday=2022/01/06

image-20220122164336440

数据库中:

image-20220122164352087

8.6 小结

分模块开发:

  • 模块中仅包含当前模块对应的功能类与配置文件
  • spring核心配置根据模块功能不同进行独立制作
  • 当前模块所依赖的模块通过导入坐标的形式加入当前模块后才可以使用
  • web.xml需要加载所有的spring核心配置文件

9 聚合和继承

9.1 聚合

聚合:多模块的构建维护。

image-20220122165852909

  • 聚合作用:聚合用于快速构建maven工程,一次性构建多个项目或模块

示例

  1. 创建一个空模块ssm,该项目中只有一个pom文件

image-20220122170016494

  1. 打包类型定义为pom,并定义当前模块进行构建时关联的其他模块名称
1
2
3
4
5
6
7
8
9
10
<!--定义该工程,用于进行构建管理-->
<packaging>pom</packaging>

<!--管理的工程列表-->
<modules>
<module>../ssm_dao</module>
<module>../ssm_pojo</module>
<module>../ssm_controller</module>
<module>../ssm_service</module>
</modules>

注意:参与聚合操作的模块最终执行顺序与模块间的依赖关系有关,与配置顺序无关

image-20220122170202646

  1. 执行mvn install并观察:
  • 各个模块的打包方式和打包顺序

image-20220122170222472

  • 打包耗时

image-20220122170257939

9.2 继承

9.2.1 概述和示例

继承:模块依赖关系维护。

image-20220122180752810

  • 继承作用:通过继承可以实现在子工程中沿用父工程的配置
  • maven中的继承与java中的继承相似,在子工程中配置继承关系

示例

  1. 在父工程ssm中声明依赖管理,将子工程所有的依赖都声明在此处。利用标签<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
26
27
28
29
30
31
32
33
34
35
<!--声明此处进行依赖管理-->
<dependencyManagement>
<!--具体的依赖-->
<dependencies>
<!--添加自己工程的依赖-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_dao</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--springmvc环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--jackson相关坐标3个-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!--只展示了部分依赖-->
</dependencies>
</dependencyManagement>
  1. 在子工程中定义父工程
1
2
3
4
5
6
7
8
9
<!--定义该工程的父工程-->
<parent>
<!--父工程的名字-->
<artifactId>ssm</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
<!--父工程的pom文件-->
<relativePath>../ssm/pom.xml</relativePath>
</parent>

此时,子工程的依赖的版本号可以省略,例如:

1
2
3
4
5
<!--导入资源文件pojo-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_pojo</artifactId>
</dependency>
  1. 同理,插件的管理也可以在父工程中声明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<build>
<pluginManagement>
<!--设置插件-->
<plugins>
<!--具体的插件配置-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
  1. 子工程中插件的版本和相关配置就可以省略
1
2
3
4
5
6
7
8
9
10
<build>
<!--设置插件-->
<plugins>
<!--具体的插件配置-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

9.2.2 继承的资源

以下资源,子工程都可以从父工程中继承:

image-20220122181446745

9.2.3 继承和聚合的区别

  • 作用:

    • 聚合用于快速构建项目
    • 继承用于快速配置
  • 相同点:

    • 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
    • 聚合与继承均属于设计型模块,并无实际的模块内容
  • 不同点:

    • 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
    • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

9.3 属性

9.3.1 自定义属性

  • 作用:等同于定义变量,方便统一维护
  • 配置位置:父工程的pom文件
  • 标签:<properties>
  • 调用格式:${ }

示例

  1. 在父工程中的pom文件内:
1
2
3
4
5
6
7
8
<!--定义自定义的属性-->
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--定义自定义的属性-->
<spring.version>5.1.9.RELEASE</spring.version>
<junit.version>4.12</junit.version>
</properties>
  1. 调用:
1
2
3
4
5
6
<!--springmvc环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

9.3.2 内置属性

  • 作用:使用maven内置属性,快速配置

例如,父工程和子工程的版本号一样,可以直接使用父工程的版本内置属性${version}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--添加自己工程的依赖-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_pojo</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_dao</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>ssm_service</artifactId>
<version>${version}</version>
</dependency>

9.3.3 其他属性

其他的属性类别还有Setting属性、Java系统属性、环境变量属性,了解即可。

9.4 版本管理

9.4.1 工程版本区分

image-20220122183813865

  • SNAPSHOT快照版本
    • 项目开发过程中,为方便团队成员合作,解决模块间相互依赖和时时更新的问题,开发者对每个模块进行构建的时候,输出的临时性版本叫快照版本(测试阶段版本)
    • 快照版本会随着开发的进展不断更新
  • RELEASE发布版本
    • 项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本

9.4.2 工程版本号约定

  • 约定规范:
    • <主版本>.<次版本>.<增量版本>.<里程碑版本>
    • 主版本∶表示项目重大架构的变更,如∶spring5相较于spring4的迭代次版本∶表示有较大的功能增加和变化,或者全面系统地修复漏洞
    • 增量版本∶表示有重大漏洞的修复
    • 里程碑版本∶表明一个版本的里程碑(版本内部)。这样的版本同下一个正式版本相比,相对来说不是很稳定,有待更多的测试
  • 范例:
    • 5.1.9.RELEASE

9.5 资源配置

资源配置多文件维护:

image-20220122184324524

配置文件引用pom属性:

  • 作用:在任意配置文件中加载pom文件中定义的属性
  • 调用格式:${ }

示例

  1. 例如jdbc.properties要读取父工程pom文件中的属性。首先在父工程pom文件中的<build>标签中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<bulid>
<!--配置资源文件对应的信息-->
<resources>
<resource>
<!--各个项目下所有的资源路径的目录-->
<directory>${project.basedir}/src/main/resources</directory>
<!--开启对配置文件的资源加载过滤-->
<filtering>true</filtering>
</resource>
</resources>

<!--针对test目录下的配置资源文件对应的信息-->
<testResources>
<testResource>
<!--各个项目下所有的资源路径的目录-->
<directory>${project.basedir}/src/test/resources</directory>
<!--开启对配置文件的资源加载过滤-->
<filtering>true</filtering>
</testResource>
</testResources>
</bulid>

设置属性:

1
2
3
4
5
6
7
8
<!--定义自定义的属性-->
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.version>5.1.9.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db?useSSL=false</jdbc.url>
</properties>
  1. jdbc.properties内容:
1
2
3
4
5
jdbc.driver=com.mysql.jdbc.Driver
# 引用pom文件中的属性
jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=12345678

9.6 多环境开发配置

多环境兼容:

image-20220122185714834

配置方法

  1. 在父工程pom文件中:
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
<!--创建多环境-->
<profiles>
<!--定义具体的环境:生产环境-->
<profile>
<!--定义环境对应的唯一名称-->
<id>pro_env</id>
<!--定义环境中使用的属性值-->
<properties>
<jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db?useSSL=false</jdbc.url>
</properties>
</profile>
<!--定义具体的环境:开发环境-->
<profile>
<!--定义环境对应的唯一名称-->
<id>dev_env</id>
<!--定义环境中使用的属性值-->
<properties>
<jdbc.url>jdbc:mysql://127.0.0.2:3306/ssm_db?useSSL=false</jdbc.url>
</properties>
<!--设置默认启动-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>
  1. 加载指定的环境
  • 调用格式:
1
mvn 指令 -P 环境id
  • 范例:
1
mvn install -P dev_env

例如:新建一个maven配置,在开发环境执行install命令:

image-20220122202158001

执行后查看jdbc.properties文件:

image-20220122202241524

可见是按照开发环境的地址127.0.0.2进行项目安装的。

9.7 跳过测试

应用场景

  1. 整体模块功能未开发
  2. 模块中某个功能未开发完毕
  3. 单个功能更新调试导致其他功能失败
  4. 快速打包(因为测试需要耗费时间)

使用操作界面跳过测试

image-20220122202910416

10 Maven私服

分模块合作开发:

image-20220122210750754

10.1 Nexus私服搭建

  • Nexus是Sonatype公司的一款maven私服产品
  • 
下载地址∶https∶//help.sonatype.com/repomanager3/download

搭建步骤

  1. 服务器首先必须配置好java的环境
  2. 将下载好的压缩包解压缩至任意位置

image-20220122211139805

  • nexus服务所需内存等参数位于/bin/nexus.vmoptions

image-20220122211252958

image-20220122211328647

  • nexus服务的端口等参数设置位于/etc/nexus-default.properties

image-20220122211436743

image-20220122211501420

  1. 配置环境变量并刷新
1
vi /etc/profile
1
2
export NEXUS_HOME=/usr/local/nexus #nexus的目录
export RUN_AS_USER=root
1
source /etc/profile
  1. 进入nexus目录并运行
1
./bin/nexus start
  1. 访问配置好的端口网址即可

image-20220122211748769

  1. 停止服务运行
1
./bin/nexus stop

10.2 Nexus操作

10.2.1 仓库分类和手工资源上传

私服资源的获取:下图所示,我们要把快照版的资源放在一个仓库里,把发行版的资源放在一个仓库里,第三方公共资源放在一个仓库里,这样方便进行管理,势必要对仓库进行分类和分组

image-20220122212618331

仓库分类

  • 宿主仓库hosted:保存无法从中央仓库获取的资源
    • 自主研发
    • 第三方非开源项目
  • 代理仓库proxy:代理远程仓库,通过nexus访问其他公共仓库,例如中央仓库
  • 仓库组group
    • 将若干仓库组成一个群组,简化配置
    • 仓库组不能保存资源,属于设计型仓库

nexus自带的仓库

操作实例

  1. 新建一个仓库,例如heima-release

image-20220122213829189

  1. 仓库类型选择宿主仓库maven2(hosted)

image-20220122213921292

  1. 创建好后,将其加入到maven-public仓库组中,方便管理

image-20220122214048929

  1. 将ssm_pojo打包好的jar文件上传至heima-release仓库中

image-20220122213802054

  1. 查看仓库里的ssm_pojo资源

image-20220122214226492

10.2.2 本地仓库访问私服

idea环境中的资源上传与下载:

image-20220123211946393

  1. 配置本地仓库访问私服的权限(setting.xml
1
2
3
4
5
6
7
8
9
10
11
12
<!--让本地仓库能够访问搭建的私服的远程仓库-->
<server>
<!--宿主仓库id-->
<id>heima-release</id>
<username>admin</username>
<password>XXXXXXXXX</password>
</server>
<server>
<id>heima-snapshot</id>
<username>admin</username>
<password>XXXXXXXXX</password>
</server>
  1. 配置本地仓库资源的下载来源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--中央仓库的资源从阿里云下载-->
<mirror>
<id>nexus-aliyun</id>
<name>nexus-aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror>

<!--其他的从私服下载-->
<mirror>
<id>heima</id>
<!--url为maven-public仓库组的url-->
<url>XXXXXXXXXXXX</url>
<mirrorOf>*</mirrorOf>
</mirror>
  1. 配置当前项目ssm访问私服上传资源的保存位置(pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--发布配置管理-->
<distributionManagement>
<repository>
<!--发布到发行版的仓库,注意这里的id必须和setting.xml配置的id相同-->
<id>heima-release</id>
<!--发行版仓库的url-->
<url>http://XXXXXXX/repository/heima-release/</url>
</repository>
<snapshotRepository>
<!--发布到快照版的仓库-->
<id>heima-snapshot</id>
<!--快照版仓库的url-->
<url>http://XXXXXXX/repository/heima-snapshot/</url>
</snapshotRepository>
</distributionManagement>
  1. 执行命令
1
mvn deploy

image-20220123214655864

  1. 执行结果

image-20220123214721101