02廖神maven笔记
-
https://liaoxuefeng.com/books/java/maven/basic/index.html
-
Maven 是一个 Java 项目管理和构建工具,它可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建,是 Java 项目不可缺少的工具。
Maven 介绍
存在问题:为什么引入 Maven?
-
因为我们项目使用的外部包很多,我们需要把外部包的
jar
包放到classpath
下,因为程序是需要读取.class 文件才能正确运行,而 jar 是.class 文件的打包所以我们需要把 jar 包放到 classpath 中。-
classpath
是什么作用- 程序运行的时候只认识.java 文件编译后的.class 字节码文件,而我们需要告诉这些文件位于哪个路径下
-
-
我们还需要确定项目的目录结构,例如,src 目录存放 Java 源码,resources 目录存放配置文件,bin 目录存放编译生成的.class 文件。
-
我们还需要配置环境,例如 JDK 的版本,编译打包的流程,当前代码的版本号
-
最后,除了使用 Eclipse 这样的 IDE 进行编译外,我们还必须能通过命令行工具进行编译,才能够让项目在一个独立的服务器上编译、测试、部署。因为在服务器上部署可没有图形化界面让你设置
- 上这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的 Java 项目管理和构建工具。这就是 Maven
Maven 特点
- 提供了标准化项目结构(项目目录结构)
- 提供了标准化的构建流程(编译,测试,打包)
- 提供了一套依赖管理机制
Maven 的缺点
- Maven 的整个体系相对庞大,想要完全掌握相对困难;
- 如果项目的依赖过多,在第一次导入项目的时候需要花很长的时间来加载所需要的依赖;
- 由于某种不可抗拒力,国内的开发者在使用 Maven 中央仓库的时候,下载速度过慢。
安装 Maven
- 廖大神的网站
maven
是一个软件,需要单独下载,只不过流行的 ide 整合了maven
Maven 目录结构
-
Maven 目录结构图:
-
项目的根目录
a-maven-project
是项目名,它有一个项目描述文件pom.xml
,存放 Java 源码的目录是src/main/java
,存放资源文件的目录是src/main/resources
,存放测试源码的目录是src/test/java
,存放测试资源的目录是src/test/resources
,最后,所有编译、打包生成的文件都放在target
目录里。这些就是一个 Maven 项目的标准目录结构。所有的目录结构都是约定好的标准结构,我们千万不要随意修改目录结构。使用标准结构不需要做任何配置,Maven 就可以正常使用。 -
项目描述文件 pom.xml
-
<project ...><modelVersion>4.0.0</modelVersion><groupId>com.itranswarp.learnjava</groupId><artifactId>hello</artifactId><version>1.0</version><packaging>jar</packaging><properties>...</properties><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency></dependencies> </project>
-
-
其中,
groupId
类似于 Java 的包名,通常是公司或组织名称,artifactId
类似于 Java 的类名,通常是项目名称,再加上version
,一个 Maven 工程就是由groupId
,artifactId
和version
作为唯一标识。我们在引用其他第三方库的时候,也是通过这 3 个变量确定。例如,依赖commons-logging
,使用<dependency>
声明一个依赖后,Maven 就会自动下载这个依赖包并把它放到 classpath 中。-
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version> </dependency>
-
依赖管理
问题:什么是项目依赖?
- 我们需要用到 a.jar,而 a.jar 自己有引用了 b.jar,Maven 这个时候会为我们自动解析判断并引入好我们需要的两个 jar 包,实际中可能引入几十个包
- 比如当我们声明一个
spring-boot-starter-web
依赖时,Maven 会自动解析并判断最终需要大概二三十个其他依赖,如果我们自己去手动管理这些依赖是非常费时费力的,而且出错的概率很大。-
spring-boot-starter-webspring-boot-starterspring-bootsprint-boot-autoconfigurespring-boot-starter-logginglogback-classiclogback-coreslf4j-apijcl-over-slf4jslf4j-apijul-to-slf4jslf4j-apilog4j-over-slf4jslf4j-apispring-coresnakeyamlspring-boot-starter-tomcattomcat-embed-coretomcat-embed-eltomcat-embed-websockettomcat-embed-corejackson-databind...
-
依赖关系
-
Maven 定义了几种依赖关系,分别是
compile
、test
、runtime
和provided
:scope 说明 示例 compile 编译时需要用到该 jar 包(默认) commons-logging test 编译 Test 时需要用到该 jar 包 junit runtime 编译时不需要,但运行时需要用到 mysql provided 编译时需要用到,但运行时由 JDK 或某个服务器提供 servlet-api
-
其中,默认的
compile
是最常用的,Maven 会把这种类型的依赖直接放入 classpath。 -
test
依赖表示仅在测试时使用,正常运行时并不需要。最常用的test
依赖就是 JUnit:-
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.2</version><scope>test</scope> </dependency>
-
-
runtime
依赖表示编译时不需要,但运行时需要。最典型的runtime
依赖是 JDBC 驱动,例如 MySQL 驱动:-
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version><scope>runtime</scope> </dependency>
-
-
provided
依赖表示编译时需要,但运行时不需要。最典型的provided
依赖是 Servlet API,编译的时候需要,但是运行时,Servlet 服务器内置了相关的 jar,所以运行期不需要-
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.0</version><scope>provided</scope> </dependency>
-
Maven 仓库原理
Maven 从哪里下载 jar 包的?
- 答案是 Maven 维护了一个中央仓库(repo1.maven.org),所有第三方库将自身的 jar 以及相关信息上传至中央仓库,Maven 就可以从中央仓库把所需依赖下载到本地。Maven 并不会每次都从中央仓库下载 jar 包。一个 jar 包一旦被下载过,就会被 Maven 自动缓存在本地目录(用户主目录的
.m2
目录),所以,除了第一次编译时因为下载需要时间会比较慢,后续过程因为有本地缓存,并不会重复下载相同的 jar 包。
唯一 ID
-
对于某个依赖,Maven 只需要 3 个变量即可唯一确定某个 jar 包:
groupId
:属于组织的名称,类似 Java 的包名;artifactId
:该 jar 包自身的名称,类似 Java 的类名;version
:该 jar 包的版本。
-
通过上述 3 个变量,即可唯一确定某个 jar 包。Maven 通过对 jar 包进行 PGP 签名确保任何一个 jar 包一经发布就无法修改。修改已发布 jar 包的唯一方法是发布一个新版本。因此,某个 jar 包一旦被 Maven 下载过,即可永久地安全缓存在本地。
-
注:只有以
-SNAPSHOT
结尾的版本号会被 Maven 视为开发版本,开发版本每次都会重复下载,这种 SNAPSHOT 版本只能用于内部私有的 Maven repo,公开发布的版本不允许出现 SNAPSHOT。
Maven 镜像
-
除了可以从 Maven 的中央仓库下载外,还可以从 Maven 的镜像仓库下载。如果访问 Maven 的中央仓库非常慢,我们可以选择一个速度较快的 Maven 的镜像仓库。Maven 镜像仓库定期从中央仓库同步:
-
slow ┌───────────────────┐┌─────────────>│Maven Central Repo.││ └───────────────────┘│ ││ │sync│ ▼ ┌───────┐ fast ┌───────────────────┐ │ User │─────────>│Maven Mirror Repo. │ └───────┘ └───────────────────┘
-
-
中国区用户可以使用阿里云提供的 Maven 镜像仓库。使用 Maven 镜像仓库需要一个配置,在用户主目录下进入
.m2
目录,创建一个settings.xml
配置文件,内容如下,配置镜像仓库后,Maven 的下载速度就会非常快。-
<settings><mirrors><mirror><id>aliyun</id><name>aliyun</name><mirrorOf>central</mirrorOf><!-- 国内推荐阿里云的Maven镜像 --><url>https://maven.aliyun.com/repository/central</url></mirror></mirrors> </settings>
-
搜索第三方组件
-
最后一个问题:如果我们要引用一个第三方组件,比如
okhttp
,如何确切地获得它的groupId
、artifactId
和version
?方法是通过 search.maven.org 搜索关键字,找到对应的组件后,直接复制:
命令行编译
-
在命令中,进入到
pom.xml
所在目录,输入以下命令,如果一切顺利,即可在target
目录下获得编译后自动打包的 jar。-
$ mvn clean package
-
构建流程
- Maven 不但有标准化的项目结构,而且还有一套标准化的构建流程,可以自动化实现编译,打包,发布,等等。
Lifecycle(生命周期) 和 Phase(阶段)
-
使用 Maven 时,我们首先要了解什么是 Maven 的生命周期(lifecycle) 。Maven 的生命周期由一系列阶段(phase)构成,以内置的生命周期
default
为例,它包含以下 phase:- validate
- initialize
- generate-sources
- process-sources
- generate-resources
- process-resources
- compile
- process-classes
- generate-test-sources
- process-test-sources
- generate-test-resources
- process-test-resources
- test-compile
- process-test-classes
- test
- prepare-package
- package
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install
- deploy
-
如果我们运行
mvn package
,Maven 就会执行default
生命周期,它会从开始一直运行到package
这个 phase 为止:- validate
- ...
- package
-
如果我们运行
mvn compile
,Maven 也会执行default
生命周期,但这次它只会运行到compile
,即以下几个 phase:- validate
- ...
- compile
-
Maven 另一个常用的生命周期是
clean
,它会执行 3 个 phase:- pre-clean
- clean (注意这个 clean 不是
lifecycle
而是phase
) - post-clean
-
所以,我们使用
mvn
这个命令时,后面的参数是 phase(比如mvn package
),Maven 自动根据生命周期运行到指定的 phase。 -
更复杂的例子是指定多个 phase,例如,运行
mvn clean package
,Maven 先执行clean
生命周期并运行到clean
这个 phase,然后执行default
生命周期并运行到package
这个 phase,实际执行的 phase 如下:- pre-clean
- clean (注意这个 clean 是 phase)
- validate
- ...
- package
-
在实际开发过程中,经常使用的命令有:
mvn clean
:清理所有生成的 class 和 jar;mvn clean compile
:先清理,再执行到compile
;mvn clean test
:先清理,再执行到test
,因为执行test
前必须执行compile
,所以这里不必指定compile
;mvn clean package
:先执行清理,再执行到package
。
-
大多数 phase 在执行过程中,因为我们通常没有在
pom.xml
中配置相关的设置(#TODO :# 需要做什么设置?),**所以这些 phase 什么事情都不做。** -
经常用到的 phase 其实只有几个:
- clean:清理
- compile:编译
- test:运行测试
- package:打包
Goal
-
其实在 Maven 的世界中,生命周期只是一个抽象的模型,其本身并不会直接去做事情,真正帮我们完成事情的是 Maven 的插件。Maven 的插件也属于构件的一种,也是可以放到 Maven 仓库当中的。
-
通常情况下,一个插件可以有 A、B、C 等等不止一个功能,但是我们又没有必要为每一个功能都做一个单独的插件。这种时候,我们一般会给这个插件绑定不同的目标,而这些目标则是对应其不同的功能。
-
-
当我们使用一个插件的目标的时候,我们可以执行命令:
mvn pluginName:goalName
。例如当我们执行dependency
插件的 list 目标的时候,我们可以执行命令:mvn dependency:list
。 -
执行一个 phase 又会触发一个或多个 goal,goal 的命名总是
abc:xyz
(插件名称:goal ) 这种形式。执行的 Phase 对应执行的 Goal mvn compile compiler:compile mvn test compiler:testCompile
surefire:test
-
看到这里,相信大家对 lifecycle、phase 和 goal 已经明白了吧?
明白个鸡儿 -
其实我们类比一下就明白了:lifecycle > phase > goal
lifecycle
相当于 Java 的 package,它包含一个或多个phase
,java 的 package 包含很多 classphase
相当于 Java 的 class,它包含一个或多个goal
,java 的 class 包含很多 methodgoal
相当于 class 的 method,它其实才是真正干活的,java 中的 methon 才是真正干活的
-
大多数情况,我们只要指定
phase
,就默认执行这些 phase 默认绑定的 goal,当然,我们也可以跳过 phase,直接使用mvn goal名称
来执行,只有少数情况,我们可以直接指定运行一个 goal,例如,启动 Tomcat 服务器:-
mvn tomcat:run
-
使用插件
标准插件
- 我们在前面介绍了 Maven 的
lifecycle
,phase
和goal
.使用 Maven 构建项目就是执行 lifecycle,执行到指定的 phase 为止。每个 phase 会执行自己默认的一个或多个 goal。 goal 是最小任务单元。 - 我们以
compile
这个 phase 为例,如果执行:-
mvn compile
-
- Maven 将执行
compile
这个 phase,这个 phase 会调用compiler
插件执行关联的compiler:compile
这个 goal。 - 实际上,执行每个 phase,都是通过某个插件(plugin)来执行的,Maven 本身其实并不知道如何执行
compile
,它只是负责找到对应的compiler
插件,然后执行默认的compiler:compile
这个 goal 来完成编译。所以,使用 Maven,实际上就是配置好需要使用的插件,然后通过 phase 调用它们。 - Maven 已经默认内置了一些常用的标准插件,不需要在
pom.xml
文件中的plugins
标签中手动声明:插件名称 对应执行的 phase clean clean compiler compile surefire test jar package
自定义插件
-
如果标准插件无法满足需求,我们还可以使用自定义插件。使用自定义插件的时候,需要声明(注意,Maven 自带的标准插件例如
compiler
是无需声明的,只有引入其它的插件才需要声明。)。例如,使用maven-shade-plugin
可以创建一个可执行的 jar,要使用这个插件,需要在pom.xml
中声明它:-
<project> ... <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration> ... </configuration></execution></executions></plugin></plugins></build> </project>
-
-
自定义插件往往需要一些配置,例如,
maven-shade-plugin
需要指定 Java 程序的入口,它的配置是:-
<configuration><transformers><transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>com.itranswarp.learnjava.Main</mainClass></transformer></transformers> </configuration>
-
-
下面列举了一些常用的插件:
- maven-shade-plugin:打包所有依赖包并生成可执行 jar;
- cobertura-maven-plugin:生成单元测试覆盖率报告;
- findbugs-maven-plugin:对 Java 源码进行静态分析以找出潜在问题。
模块管理
这里的步骤是不借助 ide 的方式手动创建一个多模块项目
-
在软件开发中,把一个大项目分拆为多个模块是降低软件复杂度的有效方法:
-
┌ ─ ─ ─ ─ ─ ─ ┐┌─────────┐│ │Module A │ │└─────────┘ ┌──────────────┐ split │ ┌─────────┐ │ │Single Project│───────> │Module B │ └──────────────┘ │ └─────────┘ │┌─────────┐│ │Module C │ │└─────────┘└ ─ ─ ─ ─ ─ ─ ┘
-
-
对于 Maven 工程来说,原来是一个大项目:
-
single-project ├── pom.xml └── src
-
-
现在可以分拆成 3 个模块:
-
mutiple-project ├── module-a │ ├── pom.xml │ └── src ├── module-b │ ├── pom.xml │ └── src └── module-c├── pom.xml└── src
-
-
Maven 可以有效地管理多个模块,我们只需要把每个模块当作一个独立的 Maven 项目,它们有各自独立的
pom.xml
。例如,模块 A 的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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.learnjava</groupId> <artifactId>module-a</artifactId> <version>1.0</version> <packaging>jar</packaging><name>module-a</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><java.version>11</java.version> </properties><dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.28</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version><scope>runtime</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.5.2</version><scope>test</scope></dependency> </dependencies>
</project>
-
-
模块 B 的
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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.learnjava</groupId> <artifactId>module-b</artifactId> <version>1.0</version> <packaging>jar</packaging><name>module-b</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><java.version>11</java.version> </properties><dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.28</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version><scope>runtime</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.5.2</version><scope>test</scope></dependency> </dependencies>
</project>
-
-
可以看出来,模块 A 和模块 B 的
pom.xml
高度相似,因此,我们可以提取出共同部分作为模块parent
:-
<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.itranswarp.learnjava</groupId> <artifactId>parent</artifactId> <version>1.0</version> <packaging>pom</packaging><name>parent</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><java.version>11</java.version> </properties><dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.28</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version><scope>runtime</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.5.2</version><scope>test</scope></dependency> </dependencies>
</project>
-
-
注意到 parent 的 <packaging> 是 pom 而不是 jar,因为
parent
本身不含任何 Java 代码。编写parent
的pom.xml
只是为了在各个模块中减少重复的配置。现在我们的整个工程结构如下:-
multiple-project ├── pom.xml--->之后解释为什么这里还有一个pom.xml文件(这里的作用是为了方便直接在multiple-project执行mvn命令时可以对下面四个模块统一处理) ├── parent │ └── pom.xml ├── module-a │ ├── pom.xml │ └── src ├── module-b │ ├── pom.xml │ └── src └── module-c├── pom.xml└── src
-
-
这样模块 A 就可以简化为:
-
<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>
<parent><groupId>com.itranswarp.learnjava</groupId><artifactId>parent</artifactId><version>1.0</version><relativePath>../parent/pom.xml</relativePath> </parent><artifactId>module-a</artifactId> <packaging>jar</packaging> <name>module-a</name>
</project>
-
-
模块 B、模块 C 都可以直接从
parent
继承,大幅简化了pom.xml
的编写。 -
如果模块 A 依赖模块 B,则模块 A 需要模块 B 的 jar 包才能正常编译,我们需要在模块 A 中引入模块 B:
-
...<dependencies><dependency><groupId>com.itranswarp.learnjava</groupId><artifactId>module-b</artifactId><version>1.0</version></dependency></dependencies>
-
-
最后,在编译的时候,需要在根目录创建一个
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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.itranswarp.learnjava</groupId> <artifactId>build</artifactId> <version>1.0</version> <packaging>pom</packaging> <name>build</name><modules><module>parent</module><module>module-a</module><module>module-b</module><module>module-c</module> </modules>
</project>
-
-
这样,在根目录执行
mvn clean package
时,Maven 根据根目录的pom.xml
找到包括parent
在内的共 4 个<module>
,一次性全部编译。 -
这里跟京东实习项目内容平台采用的模块化管理并不一致,二者各有各的优缺点
依赖冲突:最短路径原则
Maven的“最近优先”原则意味着在解析依赖冲突时,将选择依赖路径中最近定义的版本。这里通过一个例子来说明这个原则是如何工作的。
假设有这样的项目结构和依赖关系:
- 项目A 依赖于 库X v1.0 和 项目B
- 项目B 依赖于 库X v2.0
项目A的pom.xml
可能包含类似以下定义的依赖:
<dependencies><!-- 依赖库X v1.0 --><dependency><groupId>com.example</groupId><artifactId>libX</artifactId><version>1.0</version></dependency><!-- 依赖项目B --><dependency><groupId>com.example</groupId><artifactId>projectB</artifactId><version>1.0</version></dependency>
</dependencies>
项目B的pom.xml
可能包含类似以下定义的依赖:
<dependencies><!-- 依赖库X v2.0 --><dependency><groupId>com.example</groupId><artifactId>libX</artifactId><version>2.0</version></dependency>
</dependencies>
在这个例子中,当Maven解析项目A的依赖时,它会发现两个不同版本的库X(v1.0和v2.0)。根据“最近优先”原则,Maven会选择依赖路径中最近的版本。因为项目A直接依赖于库X v1.0,而库X v2.0是通过项目B间接依赖的,所以Maven会选择库X v1.0作为解决方案。
这个原则有助于减少因依赖传递而造成的版本冲突,但有时也可能导致不是最新版本的库被使用。如果需要使用特定版本的库,可以在项目A的pom.xml
中显式指定所需版本,或者使用<dependencyManagement>
元素在父POM中统一管理依赖版本。
单元测试
在我们平时开发的过程中,测试环节是永远不能避免的。那我们如何能够快速的进行单元测试呢,如何更方便的看到测试结果呢?在这个过程中,Maven 也能够为我们提供帮助,那我们来看看 Maven 如何在测试环节来辅助我们的。
1. 使用 Maven 进行单元测试
1.1 添加测试代码
这里面我们在 mall-order 模块中增加生成订单的方法,这个方法调用后会生成一个 OrderEntity 对象,里面包括订单编号(orderNuM)和订单所有者(orderOwner)两个字段。(这里我们仅仅是用来模拟单元测试,对业务的具体逻辑不做过多纠结)
首先,我们在 mall-order 模块的 pom.xml 文件中添加需要的依赖。
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId>
</dependency>
12345678
添加依赖后,我们在项目中创建 service 目录和 entity 目录,分别用于存放项目的服务层代码和实体类。
在项目中添加对应的 service 和 entity
并且在对应的测试目录中,增加该服务层代码的测试类 OrderServiceTest
。
完成后,我们可以执行该测试用例,来调试 OrderService
中的 generateOrder
方法。
从调试结果来看,我们的方法被成功调用,并且没有异常。
1.2 借助 Maven 进行单元测试
后来,随着我们项目的不断进行,我们开发的功能也随之不断增多,相应的,不同功能的测试用例也在不断的增多。这个时候,如果单纯的靠开发人员手工去点击每一个测试用例,这显然是不合理的。
那么我们就可以借助 Maven 来帮助我们做这件事情,来进行自动化的单元测试。
例如在 mall-order 模块下, 我们想要执行所有的单元测试用例,那么我们只需要进入到该模块的根目录下,执行 mvn test
命令即可。
[INFO] Scanning for projects... [INFO] [INFO] ----------------------< com.mic.tech:mall-order >----------------------- [INFO] Building mall-order 1.0.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] ... [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mall-order --- [INFO] Surefire report directory: D:\code\mall-aggregate\mall-order\target\surefire-reports
T E S T S
Running com.mic.tech.OrderServiceTest
...
Results :Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.333 s
[INFO] Finished at: 2020-05-20T23:37:40+08:00
[INFO] ------------------------------------------------------------------------
12345678910111213141516171819202122232425
从执行结果,我们可以看出,一共执行了三个测试用例,没有失败,也没有报错的情况出现。
2. 跳过测试
2.1 指定测试用例进行测试
其实每一项新的操作一般都会伴随一些问题产生。例如,我们在实际的开发过程中,有些时候只是改动了一处代码,但是如果直接执行 mvn test
命令的话,会将整个项目的测试用例全部都执行一遍,这对于我们来说,是有些得不偿失的,没必要因为一处改动,而去测试其他几十个或者几百个测试用例。
那我们应该怎么办呢? 这里我们为了演示,写了两个测试类,OrderServiceTest
和 OrderService2Test
,其中第一个类中,有两个测试用例,第二个类中,只有一个测试用例。
这时候,我们修改了第二个类中测试用例对应的方法,需要重新进行单元测试。我们可以直接执行命令:mvn test -Dtest=OrderService2Test
:
[INFO] Scanning for projects... [INFO] [INFO] ----------------------< com.mic.tech:mall-order >----------------------- [INFO] Building mall-order 1.0.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] ... [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mall-order --- [INFO] Surefire report directory: D:\code\mall-aggregate\mall-order\target\surefire-reports
T E S T S
Running com.mic.tech.OrderService2Test
...
Results :Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.261 s
[INFO] Finished at: 2020-05-21T22:17:47+08:00
[INFO] ------------------------------------------------------------------------
12345678910111213141516171819202122232425
从结构来看,我们这里只执行了第二个测试类中的测试用例。
2.2 跳过测试
换到另外一个场景,构建项目的时候。在平时的开发过程中,我们经常会使用 mvn package
构建项目,但是如果这个项目比较庞大,测试用例会非常多,那么执行测试用例的过程就会非常耗时。那怎么办呢,test 阶段在 package 阶段之前,如果直接执行 package 阶段,test 阶段势必会被执行到。
这个时候我们可以跳过测试来构建项目。(当然,这样的做法是不被建议的)
在执行构建命令的时候,添加参数来指定跳过测试即可,mvn package -DskipTests
或者 mvn package -Dmaven.test.skip=true
。
这两个命令虽然都能够在构建项目的时候跳过测试,但还是有些区别的。
- -DskipTests: 会编译测试类;
- -Dmaven.test.skip=true: 不会编译测试类。
3. 测试报告
Maven 的默认配置中,会在 target\surefire-reports
目录下生成测试报告。
我们执行 mvn clean test
,就可以观察到该目录生成。
我们可以在 txt 格式的文档中看到生成的测试报告。这里的测试报告基本上和控制台输出的内容是类似的。
大家可能也注意到了,我们在执行测试用例的时候,同时生成了两种类型的文件,一种是 txt 格式,另一个则是 XML 格式。
- txt 格式: 为了让执行测试用例的开发者更加直观的看到测试用例的运行结果;
- XML 格式: 更多的是为了支持其他工具的解析。
4. maven-surefire-plugin
说了这么多,其实 Maven 之所以可以帮助我们自动执行测试用例,还是依靠 maven-surefire-plugin 插件来实现的。在学过 Maven 的生命周期之后,我们知道一个插件的功能都是通过目标来实现的,而不同的目标则会和生命周期中的不同阶段进行绑定。这里,生命周期中的 test 阶段就是和 maven-surefire-plugin 插件的 test 目标进行绑定的。
对于 Maven 来说,我们可以不指定或者显示的声明该插件。
在显示声明的时候,我可以通过添加 configuration 的方式来实现刚刚执行命令的效果。
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.17</version><configuration><skipTests>true</skipTests></configuration></plugin></plugins>
</build>
123456789101112
例如我们在 configuration 中添加 skipTests 节点,则可以默认跳过测试。当我们再次执行 mvn package
命令构建项目的时候,test 阶段是不会被执行的。
当然,我们也可以在 configuration 中添加 include 节点和 exclude 节点,来控制执行的测试类。
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.17</version><configuration><includes><include>**/OrderServiceTest.java</include></includes><excludes><exclude>**/OrderService2Test.java</exclude></excludes></configuration>
</plugin>
12345678910111213
此时,我们再次执行 mvn test
的时候会发现只有 OrderServiceTest.java
类中的测试用例被执行了。
5. 小结
在本节的学习中,我们简单介绍了如何编写测试用例,Maven 如何自动的执行测试用例,以及在执行测试用例的过程中的一些技巧,比如跳过测试。最后我们还介绍了 maven-surefire-plugin 的简单使用。通过本节的学习,我们可以使用 Maven 轻松的进行自动化测试。
Profile 构建
上一节,我们讲到了如何使用 Maven 自动化的进行单元测试,那测试通过之后,我们就要进行构建,并且将构建好的程序包,放到对应的服务器上去运行。这个时候,问题就出现了,对于不同环境,我们需要不同的配置文件,那我们构建的之前,就需要将配置文件改为对应环境的配置文件,之后才能进行构建。总而言之,很麻烦,而且,万一忘记改配置,那么构建出来的包就是错的。
Profile 则让不同环境之间可移植性的构建成为可能。它使我们在构建项目的时候,可以很轻松的在不同环境中进行构建,也可以称之为“开箱即用”。
1. 可移植性
首先,我们来介绍一下可移植性。所谓的可移植性,指的是,在构建的过程中,改动配置文件的次数和范围越小,则可移植性越强,反之,则可移植性越弱。根据可移植性的不同程度,我们可以将其划分为如下几类:
- 不可移植: 指的是,项目只能在某个特定环境或者条件下才能构建。这种时候,构建是不可移植的,当然,我们在开发的过程中,肯定是不想看到这种情况的发生。
- 环境可移植: 指的是,对于不同环境添加不同的配置,一次构建都能够完成,那么这个构建就具备环境可移植了。即:无需对不同环境做过多改动,即可完成相应构建。
- 全局可移植: 指的是,无论在何种环境下,开发者都不需要做任何配置,即可完成对项目的构建工作。这个特性对于优秀的开源软件来说,尤其重要。因为这种类型的软件,往往会由来自各地的开发者来共同开发。
在大多数情况下,我们平时开发的项目只需要做到环境可移植就可以了。因为通常的公司往往会有三套环境,开发环境,测试环境,生产环境,针对不同的开发阶段,往往需要将项目构建到不同的环境中去,但是由于这些项目通常部署在公司的内网环境中,所以,我们并不需要考虑做到全局可移植性。
2. Maven Profile
在了解了什么是可移植性之后,那我们来看看 Maven 是如何实现可移植性的。这里就需要介绍 Maven 的一组配置 Profile 。通过使用 Profile 我们就可以实现针对不同环境自定义进行构建。通常情况下,Profile 被定义在 pom.xml 文件中,但是定义的方式也可以有很多种。
<profiles><profile><id>dev</id><properties><database.driver>com.mysql.cj.jdbc.Driver</database.driver><database.url>jdbc:mysql://localhost:3306/dev</database.url><database.username>Mic</database.username><database.password>Abc123</database.password></properties></profile><profile><id>test</id><properties><database.driver>com.mysql.cj.jdbc.Driver</database.driver><database.url>jdbc:mysql://localhost:3306/test</database.url><database.username>Mic</database.username><database.password>Abc123</database.password></properties></profile>
</profiles>
这里我们以数据库连接串为例,可以将不同环境中的数据库连接串配置到对应的 profile 节点中,并为每个 profile 节点定义单独的 id 。配置好之后,我们我们在进行构建项目的时候,可以在执行命令的时候,加上参数 -Pdev 来指定对应的配置 :mvn clean package -Pdev
。
但是问题来了,通常情况下,我们不会把配置信息放到 pom.xml 文件中,这样对于我们管理配置,并不方便。那我们如何在构建的时候,指定使用对应的配置文件来进行构建呢?我们可以对 pom.xml 文件进行简单的修改。
<!-- build决定打包时打包哪些配置文件 --> <build> <resources><resource><directory>src/main/resources/</directory><!-- 打包时,将对应配置文件先排除 --><excludes><exclude>**/*.yml</exclude></excludes><includes><!--如果有其他定义通用文件,需要包含进来--></includes></resource><resource><!-- 通过自定义的节点来激活指定的配置文件,profile.activ取值dev和test --><directory>src/main/resources/${profile.active}</directory></resource></resources> </build>
<!-- profiles配置了各种运行环境 -->
<profiles>
<profile>
<id>dev</id>
<properties>
<!-- 自定义节点profile.active-->
<profile.active>dev</profile.active>
</properties>
<!--activation用来指定激活方式,可以根据jdk环境,环境变量,文件的存在或缺失-->
<activation>
<!-- 表示默认激活-->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>
首先,我们定义 <resource>
节点用于指定配置文件的目录,并将最后一级目录定义成可配置的(如 src/main/resources/${profile.active}
)。然后,跟刚刚类似,我们开始定义 <profiles>
节点,只不过这次,我们只是在 properties
节点中自定义了一个节点(即 <profile.active>dev</profile.active>
),与 resource
中的自定义路径相呼应(自定义路径分别是 dev
和 test
)。相应的,我们需要在 resources
目录下,创建两个目录,分别为 dev
和 prod
目录,用于存放不同的配置文件。
都配置好之后,我们再使用命令 mvn clean package -Pdev
来构建项目。构建完成后,我们查看目录 target\classes
会发现,application-dev.yml
配置文件被编译进来,而另一个配置文件并没有被编译进来。这时候,我们的目标就基本上就达成了。
注意 :由于我们这里配置的 dev Profile
是默认激活状态的,所以执行 mvn clean package
和 mvn clean package -Pdev
两个命令的结果是相同的。
当然,通过配置 profile
,我们不仅仅可以指定激活不同的配置文件,也可以指定构建使用的 JDK 版本,以及某些操作系统的参数。
3. 小结
本节中,我们主要介绍了 Maven 的一个属性,叫做 Profile 。通过配置 Profile 我们可以实现构建的环境可移植性,从而大大简化我们在构建过程中的便捷程度,让我们从重复的修改配置的过程中解脱出来。
Maven 属性与资源过滤
在之前的章节中,我们已经介绍了 Maven 如何使用 Profile 来进行构建。类似的,对于 Maven 来说,还有很多其他的资源或者属性,而这些资源或者属性也都是可以进行过滤的,这一小节中,我们就重点介绍一下在什么情况下,需要过滤这些,并且要如何进行操作。
1. 属性
首先,我们来介绍一下 Maven 的属性特性。其实,在我们之前的章节中,一直都有在使用 Maven 的属性。例如我们在引入 Spring 框架的时候,将 Spring 框架的版本号信息抽象出来,放到 properties
节点中去,在使用这个版本号的时候,可以通过 ${}
来引用。
<properties><spring.version>4.0.2.RELEASE</spring.version>
</properties>
<dependencyManagement><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency></dependencies>
</dependencyManagement>
我们都知道抽象了 spring.version
属性之后,可以减少我们很多的工作量,而且也便于我们对 pom.xml 文件的管理。在 Maven 的世界中,这只是其中一种属性。那我们来介绍一下 Maven 的属性的种类。
- 内置属性: Maven 的内置属性主要有两个,一个是
${basedir}
用来表示项目的根目录,另一个是${version}
用来表示项目的版本号; - POM 属性: 用来引用 pom.xml 文件中对应元素的值。一般来说,可以用
${project.*}
来表示,例如:${project.groupId}
就是用来表示项目的 groupId 信息; - 自定义属性: 这个比较容易理解,就像我们上面例子中的
${spring.version}
就属于自定义属性的范围; - Settings 属性: 与 POM 属性类似。通常使用
${settings.*}
来表示,Settings 属性用来指向 settings.xml 文件中的属性,例如:${settings.localrepository}
可以用来表示本地仓库的地址; - Java 系统属性: 所有 Java 的系统属性都可以通过 Maven 属性来引用。我们在使用之前可以通过
mvn help:system
命令来查看对应的属性; - 环境变量属性: 所有的环境变量属性都可以通过 Maven 属性来引用。通常用
${env.*}
来表示。
我们在很多地方都可以用到 Maven 属性,例如我们的示例项目中,多模块直接互相引用的时候,我们会用到 ${project.groupId}
和 ${project.version}
,来管理项目内部依赖。会用到 ${project.basedir}
来指定项目配置文件的路径。
2. Profile
这里就是之前章节中讲到的 Profile
,所以在这里,我们就不做过多的介绍,可以移步到Profile 构建一节进行学习。
3. 资源过滤
我们使用 Maven 资源过滤的目的和使用 Profile 的目的很大程度上是类似的,就是为了增强项目构建的可移植性。之前我们在 Profile 的章节中,讲到了在构建项目的时候,激活对应的 Profile 来使用对应的配置,当时我们把配置都放在了配置文件中,因此,如果有多套环境的话,那么配置文件就相应的需要多套。
也就是之前Profile 构建的方式配置多个环境需要多个不同的配置文件,build 只会激活多个配置文件中的一个;使用资源过滤的方式只需要一个配置文件,通过变量的形式,根据不同的环境给 resource/
目录下的配置文件设置不同的值
这里,我们换一种方式来进行资源过滤。在讲 Profile 的章节中,我们使用 mall-order
模块来演示,这次我们换为 mall-delivery
模块来演示。
首先在 src\main\resources
目录下添加 application.yml
文件,用作配置文件。文件中的内容如下:
spring:datasource:driver-class-name: ${database.driverClass}username: ${database.username}password: ${database.password}url: ${database.url}type: com.alibaba.druid.pool.DruidDataSource
这里,可以看到,我们使用了 ${}
的方式来引用属性,这个属性可以定义在 pom.xml
文件中。
接下来,我们就在 pom.xml
文件中配置对应的属性。
<build><resources><resource><directory>src/main/resources</directory><filtering>true</filtering></resource></resources> </build>
<profiles>
<profile>
<id>dev</id>
<properties>
<database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass>
<database.url>jdbc:mysql://localhost:3306/dev</database.url>
<database.username>userNameDev</database.username>
<database.password>passwordDev</database.password>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass>
<database.url>jdbc:mysql://localhost:3307/test</database.url>
<database.username>userNameTest</database.username>
<database.password>passwordTest</database.password>
</properties>
</profile>
</profiles>
通常情况下,资源过滤是关闭的,如果需要开启,则需要我们手动设置 <filtering>true</filtering>
。默认关闭也是为了防止在构建的过程中发生一些不必要的过滤情况。
这里,我们分别配置了开发环境的数据库信息和测试环境的数据库信息(其实也是使用的 Profile 的方式)。其中 properties 节点配置了我们自定义的属性,其与我们刚刚在 application.yml
文件中配置的占位符是一样的。
接下来,我们进行项目构建可以指定对应的 Profile,执行 Maven 命令 mvn clean package -Pdev
。构建完成后,我们可以查看 target\classes
目录下的构建结果,会发现配置文件中的确是用的开发环境的 Profile,目的达成。
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: userNameDevpassword: passwordDevurl: jdbc:mysql://localhost:3306/devtype: com.alibaba.druid.pool.DruidDataSource
以上的配置,还可以进行一下修改。由于我们平时更多的是在用开发环境的配置,因此,我们可以把开发环境的配置单独放置出来。
<build>... </build> <properties><database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass><database.url>jdbc:mysql://localhost:3306/dev</database.url><database.username>userNameDev</database.username><database.password>passwordDev</database.password> </properties>
<profiles>
<profile>
<id>test</id>
<properties>
<database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass>
<database.url>jdbc:mysql://localhost:3307/test</database.url>
<database.username>userNameTest</database.username>
<database.password>passwordTest</database.password>
</properties>
</profile>
</profiles>
这样修改之后,我们在进行开发环境构建的时候,不需要添加额外的参数,直接执行命令 mvn clean package
即可正常构建。而当我们需要构建其他环境程序包的时候,则在命令后面添加对应的参数。
4. 小结
本文,我们承接《Maven 使用 Profile 构建》一节,继续介绍 Maven 的属性和资源过滤,学了这两节之后,能够更方便,更明晰的管理 pom.xml 文件中的依赖,也让多环境构建变得更加简单。
使用 mvnw
- 我们使用 Maven 时,基本上只会用到
mvn
这一个命令。有些童鞋可能听说过mvnw
,这个是啥? mvnw
是 Maven Wrapper 的缩写。因为我们安装Maven
时,默认情况下,系统所有项目都会使用全局安装的这个Maven
版本。但是,对于某些项目来说,它可能必须使用某个特定的Maven
版本,这个时候,就可以使用 Maven Wrapper,它可以负责给这个特定的项目安装指定版本的 Maven,而其他项目不受影响。- 简单地说,Maven Wrapper 就是给一个项目提供一个独立的,指定版本的 Maven 给它使用。
安装 Maven Wrapper
-
安装
Maven Wrapper
最简单的方式是在项目的根目录(即pom.xml
所在的目录)下运行安装命令,它会自动使用最新版本的 Maven。注意0.7.6
是 Maven Wrapper 的版本。最新的 Maven Wrapper 版本可以去官方网站查看。-
mvn -N io.takari:maven:0.7.6:wrapper
-
-
如果要指定使用的 Maven 版本,使用下面的安装命令指定版本,例如
3.3.3
:-
mvn -N io.takari:maven:0.7.6:wrapper -Dmaven=3.3.3
-
-
安装后,查看项目结构:
-
my-project ├── .mvn │ └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main │ ├── java │ └── resources └── test ├── java └── resources
-
-
发现多了
mvnw
文件、mvnw.cmd
文件 和.mvn
目录,我们只需要把mvn
命令改成mvnw
就可以使用跟项目关联的 Maven。例如:-
mvnw clean package
-
-
在 Linux 或 macOS 下运行时需要加上
./
:-
./mvnw clean package
-
-
Maven Wrapper 的另一个作用是把项目的
mvnw
、mvnw.cmd
和.mvn
提交到版本库中,可以使所有开发人员使用统一的 Maven 版本。
发布 Artifact
- 当我们使用
commons-logging
这些第三方开源库的时候,我们实际上是通过 Maven 自动下载它的 jar 包,并根据其pom.xml
解析依赖(compile,test,runtime,provided
),自动把相关依赖包都下载后加入到 classpath。 - 那么问题来了:当我们自己写了一个牛逼的开源库时,非常希望别人也能使用,总不能直接放个 jar 包的链接让别人下载吧?
- 如果我们把自己的开源库放到 Maven 的 repo 中,那么,别人只需按标准引用
groupId:artifactId:version
,即可自动下载 jar 包以及相关依赖。因此,本节我们介绍如何发布一个库到 Maven 的 repo 中。 - 把自己的库发布到 Maven 的 repo 中有好几种方法,我们介绍 3 种最常用的方法。
第一种方法:以静态文件发布
仓库目录结构分析
-
如果我们观察一个中央仓库的 Artifact 结构,例如 Commons Math,它的 groupId 是
org.apache.commons
,artifactId 是commons-math3
,以版本3.6.1
为例,发布在中央仓库的文件夹路径就是 https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.6.1/,在此文件夹下,commons-math3-3.6.1.jar
就是发布的 jar 包,commons-math3-3.6.1.pom
就是它的pom.xml
描述文件,commons-math3-3.6.1-sources.jar
是源代码,commons-math3-3.6.1-javadoc.jar
是文档。其它以.asc
、.md5
、.sha1
结尾的文件分别是 GPG 签名、MD5 摘要和 SHA-1 摘要。我们只要按照这种目录结构组织文件,它就是一个有效的 Maven 仓库。
以静态文件发布到 repo 举例
-
我们以广受好评的开源项目 how-to-become-rich 为例,先创建 Maven 工程目录结构如下:
-
how-to-become-rich ├── maven-repo <-- Maven本地文件仓库 ├── pom.xml <-- 项目文件 ├── src │ ├── main │ │ ├── java <-- 源码目录 │ │ └── resources <-- 资源目录 │ └── test │ ├── java <-- 测试源码目录 │ └── resources <-- 测试资源目录 └── target <-- 编译输出目录
-
-
在
pom.xml
中添加如下内容:-
<project ...>...<distributionManagement><repository><id>local-repo-release</id><name>GitHub Release</name><url>file://${project.basedir}/maven-repo</url></repository></distributionManagement>
<build><plugins><plugin><artifactId>maven-source-plugin</artifactId><executions><execution><id>attach-sources</id><phase>package</phase><goals><goal>jar-no-fork</goal></goals></execution></executions></plugin><plugin><artifactId>maven-javadoc-plugin</artifactId><executions><execution><id>attach-javadocs</id><phase>package</phase><goals><goal>jar</goal></goals></execution></executions></plugin></plugins> </build>
</project>
-
-
注意到
<distributionManagement>
,它指示了发布的软件包的位置,这里的<url>
是项目根目录下的maven-repo
目录,在<build>
中定义的两个插件maven-source-plugin
和maven-javadoc-plugin
分别用来创建源码和 javadoc,如果不想发布源码,可以把对应的插件去掉。 -
我们直接在项目根目录下运行 Maven 命令
mvn clean package deploy
,如果一切顺利,我们就可以在maven-repo
目录下找到部署后的所有文件如下:-
maven-repo └── com└── itranswarp└── rich└── how-to-become-rich├── 1.0.0│ ├── how-to-become-rich-1.0.0-javadoc.jar│ ├── how-to-become-rich-1.0.0-javadoc.jar.md5│ ├── how-to-become-rich-1.0.0-javadoc.jar.sha1│ ├── how-to-become-rich-1.0.0-sources.jar│ ├── how-to-become-rich-1.0.0-sources.jar.md5│ ├── how-to-become-rich-1.0.0-sources.jar.sha1│ ├── how-to-become-rich-1.0.0.jar│ ├── how-to-become-rich-1.0.0.jar.md5│ ├── how-to-become-rich-1.0.0.jar.sha1│ ├── how-to-become-rich-1.0.0.pom│ ├── how-to-become-rich-1.0.0.pom.md5│ └── how-to-become-rich-1.0.0.pom.sha1├── maven-metadata.xml├── maven-metadata.xml.md5└── maven-metadata.xml.sha1
-
-
最后一步,是把这个工程推到 GitHub 上,并选择
Settings
-GitHub Pages
,选择master branch
启用 Pages 服务: -
这样,把全部内容推送至 GitHub 后,即可作为静态网站访问 Maven 的 repo,它的地址是 https://michaelliao.github.io/how-to-become-rich/maven-repo/。版本
1.0.0
对应的 jar 包地址是:-
https://michaelliao.github.io/how-to-become-rich/maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar
-
-
现在,如果其他人希望引用这个 Maven 包,我们可以告知如下依赖即可:
-
<dependency><groupId>com.itranswarp.rich</groupId><artifactId>how-to-become-rich</artifactId><version>1.0.0</version> </dependency>
-
-
但是,除了正常导入依赖外,对方还需要再添加一个
<repository>
的声明,即使用方完整的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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion>
<groupId>example</groupId> <artifactId>how-to-become-rich-usage</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><java.version>11</java.version> </properties><repositories><repository><id>github-rich-repo</id><name>The Maven Repository on Github</name><url>https://michaelliao.github.io/how-to-become-rich/maven-repo/</url></repository> </repositories><dependencies><dependency><groupId>com.itranswarp.rich</groupId><artifactId>how-to-become-rich</artifactId><version>1.0.0</version></dependency> </dependencies>
</project>
-
-
在
<repository>
中,我们必须声明发布的 Maven 的 repo 地址,其中<id>
和<name>
可以任意填写,<url>
填入 GitHub Pages 提供的地址 +/maven-repo/
后缀。现在,即可正常引用这个库并编写代码如下:-
Millionaire millionaire = new Millionaire(); System.out.println(millionaire.howToBecomeRich());
-
-
有的童鞋会问,为什么使用
commons-logging
等第三方库时,并不需要声明 repo 地址?这是因为这些库都是发布到 Maven 中央仓库的,发布到中央仓库后,不需要告诉 Maven 仓库地址,因为它知道中央仓库的地址默认是 https://repo1.maven.org/maven2/,也可以通过~/.m2/settings.xml
指定一个代理仓库地址以替代中央仓库来提高速度(参考依赖管理的 Maven 镜像)。 -
因为 GitHub Pages 并不会把我们发布的 Maven 包同步到中央仓库,所以自然使用方必须手动添加一个我们提供的仓库地址。
-
此外,通过 GitHub Pages 发布 Maven repo 时需要注意一点,即不要改动已发布的版本。因为 Maven 的仓库是不允许修改任何版本的,对一个库进行修改的唯一方法是发布一个新版本。但是通过静态文件的方式发布 repo,实际上我们是可以修改 jar 文件的,但最好遵守规范,不要修改已发布版本。
第二种方法:通过 Nexus 发布到中央仓库
-
有的童鞋会问,能不能把自己的开源库发布到 Maven 的中央仓库,这样用户就不需要声明 repo 地址,可以直接引用,显得更专业。
-
当然可以,但我们不能直接发布到 Maven 中央仓库,而是通过曲线救国的方式,发布到 central.sonatype.org,它会定期自动同步到 Maven 的中央仓库。Nexus 是一个支持 Maven 仓库的软件(就像是 maven repository 网站一样,Nexus 可以部署一个本地的 maven repository 网站),由 Sonatype 开发,有免费版和专业版两个版本,很多大公司内部都使用 Nexus 作为自己的私有 Maven 仓库,而这个 central.sonatype.org 相当于面向开源的一个 Nexus 公共服务。
-
所以,第一步是在 central.sonatype.org 上注册一个账号,注册链接非常隐蔽,可以自己先找找,找半小时没找到点这里查看攻略。
-
如果注册顺利并审核通过,会得到一个登录账号,然后,通过这个页面一步一步操作就可以成功地将自己的 Artifact 发布到 Nexus 上,再耐心等待几个小时后,你的 Artifact 就会出现在 Maven 的中央仓库中。
-
-
这里简单提一下发布重点与难点:
- 必须正确创建 GPG 签名,Linux 和 Mac 下推荐使用 gnupg2;
- 必须在
~/.m2/settings.xml
中配置好登录用户名和口令,以及 GPG 口令:-
<settings ...>...<servers><server><id>ossrh</id><username>OSSRH-USERNAME</username><password>OSSRH-PASSWORD</password></server></servers><profiles><profile><id>ossrh</id><activation><activeByDefault>true</activeByDefault></activation><properties><gpg.executable>gpg2</gpg.executable><gpg.passphrase>GPG-PASSWORD</gpg.passphrase></properties></profile></profiles> </settings>
-
-
在待发布的 Artifact 的
pom.xml
中添加 OSS 的 Maven repo 地址,以及maven-jar-plugin
、maven-source-plugin
、maven-javadoc-plugin
、maven-gpg-plugin
、nexus-staging-maven-plugin
:-
<project ...>...<distributionManagement><snapshotRepository><id>ossrh</id><url>https://oss.sonatype.org/content/repositories/snapshots</url></snapshotRepository>
<repository><id>ossrh</id><name>Nexus Release Repository</name><url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url></repository> </distributionManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><executions><execution><goals><goal>jar</goal><goal>test-jar</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-source-plugin</artifactId><executions><execution><id>attach-sources</id><goals><goal>jar-no-fork</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-javadoc-plugin</artifactId><executions><execution><id>attach-javadocs</id><goals><goal>jar</goal></goals><configuration><additionalOption><additionalOption>-Xdoclint:none</additionalOption></additionalOption></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-gpg-plugin</artifactId><executions><execution><id>sign-artifacts</id><phase>verify</phase><goals><goal>sign</goal></goals></execution></executions></plugin><plugin><groupId>org.sonatype.plugins</groupId><artifactId>nexus-staging-maven-plugin</artifactId><version>1.6.3</version><extensions>true</extensions><configuration><serverId>ossrh</serverId><nexusUrl>https://oss.sonatype.org/</nexusUrl><autoReleaseAfterClose>true</autoReleaseAfterClose></configuration></plugin></plugins> </build>
</project>
-
-
最后执行命令
mvn clean package deploy
即可发布至 central.sonatype.org。 -
此方法前期需要复杂的申请账号和项目的流程,后期需要安装调试 GPG,但只要跑通流程,后续发布都只需要一行命令。
第四种方法:发布到私有仓库
通过 nexus-staging-maven-plugin
除了可以发布到 central.sonatype.org 外,也可以发布到私有仓库,例如,公司内部自己搭建的 Nexus 服务器。
如果没有私有 Nexus 服务器,还可以发布到 GitHub Packages。GitHub Packages 是 GitHub 提供的仓库服务,支持 Maven、NPM、Docker 等。使用 GitHub Packages 时,无论是发布 Artifact,还是引用已发布的 Artifact,都需要明确的授权 Token,因此,GitHub Packages 只能作为私有仓库使用。
在发布前,我们必须首先登录后在用户的 Settings
-Developer settings
-Personal access tokens
中创建两个 Token,一个用于发布,一个用于使用。发布 Artifact 的 Token 必须有 repo
、write:packages
和 read:packages
权限:
使用 Artifact 的 Token 只需要 read:packages
权限。
在发布端,把 GitHub 的用户名和发布 Token 写入 ~/.m2/settings.xml
配置中:
<settings ...>...<servers><server><id>github-release</id><username>GITHUB-USERNAME</username><password>f052...c21f</password></server></servers>
</settings>
然后,在需要发布的 Artifact 的 pom.xml
中,添加一个 <repository>
声明:
<project ...>...<distributionManagement><repository><id>github-release</id><name>GitHub Release</name><url>https://maven.pkg.github.com/michaelliao/complex</url></repository></distributionManagement>
</project>
注意到 <id>
和 ~/.m2/settings.xml
配置中的 <id>
要保持一致,因为发布时 Maven 根据 id 找到用于登录的用户名和 Token,才能成功上传文件到 GitHub。我们直接通过命令 mvn clean package deploy
部署,成功后,在 GitHub 用户页面可以看到该 Artifact:
完整的配置请参考 complex 项目,这是一个非常简单的支持复数运算的库。
使用该 Artifact 时,因为 GitHub 的 Package 只能作为私有仓库使用,所以除了在使用方的 pom.xml
中声明 <repository>
外:
<project ...>...<repositories><repository><id>github-release</id><name>GitHub Release</name><url>https://maven.pkg.github.com/michaelliao/complex</url></repository></repositories>
<dependencies><dependency><groupId>com.itranswarp</groupId><artifactId>complex</artifactId><version>1.0.0</version></dependency> </dependencies> ...
</project>
还需要把有读权限的 Token 配置到 ~/.m2/settings.xml
文件中。
pom.xml 文件中的 标签
前言
在阅读详细文档之前我们先来谈谈我自己对 maven 的一些个人理解,以助于从整体大局上了解 maven。
-
maven 是什么,用通俗的话来将,maven 能帮你构建工程,管理 jar 包,编译代码,还能帮你自动运行单元测试,打包,生成报表,甚至能帮你部署项目
-
使用 maven 构建的项目均可以直接使用 maven build 标签完成项目的编译测试打包,无需额外配置
-
Maven 是通过 pom.xml 来执行任务的,其中的
build 标签
描述了如何来编译及打包项目,而具体的编译和打包工作是通过 build 中配置的 plugin 来完成。当然 plugin 配置不是必须的,默认情况下,Maven 会绑定以下几个插件来完成基本操作。-
即在没有配置的情况下,执行
mvn clean install
时,maven 会调用默认的 plugin 来完成编译打包操作,具体来讲,执行 mvn clean install 时会执行 -
maven-clean-plugin:2.5:clean (default-clean)
-
maven-resources-plugin:2.6:resources (default-resources)
-
maven-compiler-plugin:3.1:compile (default-compile)
-
maven-resources-plugin:2.6:testResources (default-testResources)
-
maven-compiler-plugin:3.1:testCompile (default-testCompile)
-
maven-surefire-plugin:2.12.4:test (default-test)
-
maven-jar-plugin:2.4:jar (default-jar)
-
maven-install-plugin:2.4:install (default-install)
-
等 plugin
-
-
-
4.如果有需要可以针对各个 plugin 进行特殊配置,需要在 pom.xml 中的
<build> / <plugins>
标签中显示指定 plugin 和 属性配置,如下配置了maven-compiler-plugin
的版本和编译时使用的 jdk 版本
POM.XML 的 build 标签
说明
在 Maven 的 pom.xml 文件中,Build 相关配置包含两个部分,一个是 <build>
,另一个是 <reporting>
,这里我们只介绍 <build>
。
1.pom.xml 中的两种 build
在 Maven 的 pom.xml 文件中,存在如下两种 <build>
:
说明:
- 一种
<build>
被称为 ==Project Build==(针对整个项目的所有情况都有效),即是<project>
的直接子元素。 - 另一种
<build>
被称为 ==Profile Build==(针对不同的profile
配置),即是<profile>
的直接子元素。
Profile Build 包含了基本的 build 元素,而 Project Build 还包含两个特殊的元素,即<...Directory> 和 <extensions>。
2.Profile Build 和 Project Build 的共有标签
共有标签:基础标签
示例如下:
说明:
defaultGoal
,执行构建时默认的 goal 或 phase,如 jar:jar 或者 package 等directory
,构建的结果所在的路径,默认为${basedir}/target
目录finalName
,构建的最终结果的名字,该名字可能在其他plugin
中被改变
共有标签: 标签
资源文件的构建
资源往往不是代码,无需编译,资源文件往往是一些 properties
或 XML
配置文件,构建过程中会往往会将资源文件从源路径复制到指定的目标路径。
标签作用
<resources>
指出了各个资源在 Maven 源码项目(不是 target/)中的具体路径。示例如下:
说明:
maven中filter和filtering的使用方法
-
filters
,给出对资源文件进行过滤的属性文件的路径,默认位于${basedir}/src/main/filters/
目录下。属性文件中定义若干键值对。在构建过程中,对于资源文件中出现的变量(键),将使用属性文件中该键对应的值替换。(资源文件指的是resource目录下的配置文件,属性文件指的是pom.xml定义的变量,属性) -
resources
, maven build 过程中涉及的资源文件 -
targetPath
,资源文件的目标路径,通常被打包在 jar 中的resources
的目标路径是META-INF
-
filtering
,构建过程中是否对资源进行过滤,默认 false -
directory
,资源文件的路径,默认位于${basedir}/src/main/resources/
目录下 -
includes
,一组文件名的匹配模式,被匹配的资源文件将被构建过程处理 -
excludes
,一组文件名的匹配模式,被匹配的资源文件将被构建过程忽略。同时被 includes 和 excludes 匹配的资源文件,将被忽略。 -
testResources
,test 过程中涉及的资源文件,默认位于${basedir}/src/test/resources/
目录下。这里的资源文件不会被构建到目标构件中 -
示例(除了 maven 默认的
src/main/resources/*.yml
配置文件需要打包之外,还需要打包src/main/java/**/*.xml
文件)
共有标签: 标签
<plugins>
给出构建过程中所用到的插件。
说明:
groupId
:插件组织名称artifactId
:插件项目名称version
:插件项目版本号extensions
,是否加载该插件的扩展,默认 falseinherited
,该插件的 configuration 中的配置是否可以被(继承该 POM 的其他 Maven 项目)继承,默认 trueconfiguration
,该插件所需要的特殊配置,在父子项目之间可以覆盖或合并dependencies
,该插件所特有的依赖类库executions
,该插件的某个 goal(一个插件中可能包含多个 goal)的执行方式。一个 execution 有如下设置:- id,唯一标识
- goals,要执行的插件的 goal(可以有多个),如
run - phase,插件的 goal 要嵌入到 Maven 的 phase 中执行,如 verify
- inherited,该 execution 是否可被子项目继承
- configuration,该 execution 的其他配置参数
共有标签: 标签
在 <build>
中,<pluginManagement>
与 <plugins>
并列,两者之间的关系类似于 <dependencyManagement>
与 <dependencies>
之间的关系。<pluginManagement>
中也配置 <plugin>
,其配置参数与 <plugins>
中的 <plugin>
完全一致。只是,<pluginManagement>
往往出现在父项目中,其中配置的 <plugin>
往往通用于子项目。子项目中只要在 <plugins>
中以 <plugin>
声明该插件,该插件的具体配置参数则继承自父项目中 <pluginManagement>
对该插件的配置,从而避免在子项目中进行重复配置。
Project Build 特有的 <...Directory>
往往配置在父项目中,供所有父子项目使用。示例如下:
目录可以使用绝对路径,如示例所示。如果使用相对路径,则所有的相对路径都是在 ${basedir}目录下。
Project Build 特有的
classpath
中。也可以通过
maven 默认标准的输入输出目录
构建 Maven 项目的时候,如果没有进行特殊的配置,Maven 会按照标准的目录结构查找和处理各种类型文件。
普通 maven 项目(非 web 项目)
src/main/java
和 src/test/java
这两个目录中的所有 *.java
文件会分别在 comile 和 test-comiple 阶段被编译,编译结果分别放到了 target/classes
和 targe/test-classes
目录下,但是这两个目录中的其他扩展名的文件都会被忽略掉。 如果在这俩目录中放置其他配置文件 mapper.xml
,dev.properties
,就会被忽略掉.
src/main/resouces
和 src/test/resources
这两个目录中的文件也会分别被复制到 target/classes
和 target/test-classes
目录下(与源码同文件夹级别)。
web 项目
当是 web 项目时,会在 target 下生成 myproject 目录,myproject 是你的项目名
src/main/webapps
src/main/webapps
这个目录中的文件会被复制到 target/myProject
目录中
target/classes
默认会把这个目录中的所有内容复制到 target/myProject/WEB-INF/classes 目录中
Dependency(项目依赖)
默认会将项目的依赖复制到 target/myProject/WEB-INF/lib
maven 仓库
-
无论哪种仓库,都是需要下载到本地磁盘才可以被项目引用.
中央仓库
其实我们使用的大多数第三方模块都是这个用法,例如,我们使用 commons logging、log4j 这些第三方模块,就是第三方模块的开发者自己把编译好的 jar 包发布到 Maven 的中央仓库中。
私有仓库
私有仓库是指公司内部如果不希望把源码和 jar 包放到公网上,那么可以搭建私有仓库。私有仓库总是在公司内部使用,它只需要在本地的 ~/.m2/settings.xml
中配置好,使用方式和中央仓位没有任何区别。
本地仓库
本地仓库是指把本地开发的项目“发布”在本地,这样其他项目可以通过本地仓库引用它。但是我们不推荐把自己的模块安装到 Maven 的本地仓库,因为每次修改某个模块的源码,都需要重新安装,非常容易出现版本不一致的情况。更好的方法是使用模块化编译,在编译的时候,告诉 Maven 几个模块之间存在依赖关系,需要一块编译,Maven 就会自动按依赖顺序编译这些模块。