一、Maven使用
1.1 背景介绍
微服务开发过程中,为了避免为每一个微服务创建一个单独的项目,可以将和项目息息相关的微服务放在一个工程中,通过Maven的多模块来组织管理。假设有这么一个场景,有三个微服务,分别为api
、 order
、price
,介绍如下:
- api:对外接口服务,这个微服务对外提供查询接口
- order:订单服务,这个微服务提供了查询所有订单的接口,以供
api
这个微服务调用 - price:计价服务,这个微服务提供了查询所有订单价格的接口,以供
order
这个微服务调用
1.2 Maven多模块
1.2.1 Maven的父模块
我们将上述背景中的三个微服务在一个项目内开发,因此首先我们创建一个Maven工程,其POM文件内容大致如下:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
com.istio.app
istio-app
0.0.1-SNAPSHOT
istio-app
pom
Istio Demo project for Spring Boot
1.8
app-api
app-order
app-price
...
...
上述的POM文件即为三个微服务的父POM文件,有几点值得注意的地方:
- 父POM文件的打包方式为
pom
,因为父POM文件仅仅为了管理子模块 - 父POM中通过
dependencyManagement
来统一管理子模块依赖的版本 - 父POM中通过
modules
来声明有哪些子模块
1.2.1 Maven的子模块
以api
这个微服务为例,它的POM文件如下面这样:
4.0.0
com.istio.app
istio-app
0.0.1-SNAPSHOT
../
app-api
0.0.1-SNAPSHOT
app-api
Demo project for Spring Boot
...
...
这个子模块的POM文件中同样也有几点值得注意的:
- 通过
parent
来指明父模块,其中relativePath
通过相对路径指向父POM文件所在位置 - 通过
dependencies
来引入子模块具体的依赖
1.3 Maven的标签
Maven的POM文件中有很多标签,下面列举下那些经常用但很容易混淆的标签
-
dependencyManagement和dependencies
:dependencyManagement只是对依赖的声明,并不会真的引入依赖,目的是为了方便统一各个子模块中相同依赖的版本号;而dependencies才会引入具体的依赖,而且如果待引入的依赖已经在父模块中通过dependencyManagement声明,那么在子模块中只需要这样即可,即不需要指定版本号
org.springframework.boot
spring-boot-starter
-
pluginManagement和plugins
:区别即联系同上述dependencyManagement和dependencies
-
dependency下的scope标签
:scope标签的取值范围包括compile、provided、runtime、system、test、import
,默认的值为compile,什么时候需要显示的指定scope标签值?在了解了Maven的生命周期后可以自行了解。 -
dependency下的type标签
:type标签的取值范围常见的包括jar、war、apk、pom
等,默认的值为jar,什么时候需要显示的指定type标签值?在了解了Maven的插件后可以自行了解。
二、Maven原理
2.1 Maven的生命周期
Maven通过其生命周期来帮助我们管理项目,我们经常执行的mvn clean package
命令,其实就是在调用maven的生命周期来帮助我们完成清理、打包等工作。Maven的声明周期包括三个:
-
clean
:完成清理工作,主要用于清理target
目录 -
default
:完成编译、打包、发布等工作 -
site
:完成文档、项目站点等工作,笔者还没用过
每个生命周期又包含多个阶段,就好比一个接口(Interface)会包含多个方法(Method),每个生命周期包含的主要阶段列举如下:
- clean:
-
clean
:clean阶段是这个生命周期内的主要阶段,我们通常执行的命令mvn clean
其实就是在执行这个阶段
-
- default:
-
process-sources
:处理源代码 -
compile
:编译源代码 -
process-test-resources
:处理测试代码 -
test-compile
:编译测试代码 -
test
:运行测试用例 -
package
:项目打包 -
install
:项目包安装到本地仓库 -
deploy
:项目包发布到远程仓库
-
关于maven的生命周期必须要明确的两点:
- 同一个生命周期内的阶段是从前往后顺序执行的,而且直接执行后面的阶段时,这个阶段前的阶段会全部按顺序执行,这也是为什么我们直接执行
mvn package
也能完成代码处理、编译、运行测试用例等功能的原因。因为compile、test这些阶段都在package阶段前。 - 不同生命周期之间是相互隔离的,即clean生命周期和default生命周期是完全无关的,这也是为什么我们通常需要执行
mvn clean package
的原因,因为clean阶段是属于clean生命周期的,如果要实现清理并打包,而这两个阶段是属于不同生命周期。
2.2 Maven的插件
基本上,我们了解了maven的生命周期后在日常开发中就可以正常使用maven来管理项目了。如果想要深究maven生命周期背后的原理,那就必须要了解maven的插件了。
我们在项目开发过程中肯定见过如下的POM文件内容:
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.8
想要理解上面的POM文件内容,也必须要了解maven的插件。
2.2.1 从Maven生命周期了解插件
我们再次回到mvn clean
这个命令,现在我们已经知道这个命令其实就是调用maven的clean生命周期内的clean阶段,我们看一下这个命令的输出:
E:\project\spring\mavenbase>mvn clean
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building maven-base 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-base ---
[INFO] Deleting E:\my-project\spring\mavenbase\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.203 s
[INFO] Finished at: 2021-05-28T11:42:16+08:00
[INFO] Final Memory: 7M/245M
[INFO] ------------------------------------------------------------------------
注意这行输出maven-clean-plugin:2.5:clean
,这行表达了三个重要信息:插件名称是maven-clean-plugin,插件版本号是2.5,插件的目标是clean。这里引入了一个新的名词插件的目标
,插件与插件目标的关系,其实就相当于生命周期与阶段的关系,插件是一个统一的接口(Interface),而插件目标是接口中的方法(Method)。
这行输出说明mvn clean
命令背后真正干活的是maven-clean-plugin
这个插件的clean这个目标,通俗的可以理解为mvn clean
调用的是maven-clean-plugin
这个接口的clean方法。
像上面这种阶段与目标的调用关系,我们可以称为绑定,不同阶段所绑定的插件目标如下:
阶段 | 插件:目标 |
---|---|
clean | maven-clean-plugin:clean |
process-resources | maven-resources-plugin:resources |
compile | maven-compiler-plugin:compile |
process-test-resources | maven-resources-plugin:testResources |
test-compile | maven-compiler-plugin:testCompile |
test | maven-surefile-plugin:test |
package | maven-jar-plugin:jar |
install | maven-install-plugin:install |
deploy | maven-deploy-plugin:deploy |
还有一个命令mvn help
,我们通常用来查询帮助信息,其本质也是绑定到了一个插件目标上,对应的是maven-help-plugin:help
2.2.2 使用Maven插件
我们通过maven-help-plugin
来详细了解一下maven官方实现的插件
mvn
: :
- 查看
maven-help-plugin
的用法
# 命令中使用的是这个插件的全称,其中包括groupId和artifactId
E:\project\spring\mavenbase>mvn org.apache.maven.plugins:maven-help-plugin:help
...
[INFO] --- maven-help-plugin:3.2.0:help (default-cli) @ maven-base
...
# 这个插件有8个目标,仅列出部分
This plugin has 8 goals:
help:describe
Displays a list of the attributes for a Maven Plugin and/or goals (aka Mojo -
Maven plain Old Java Object).
help:effective-pom
Displays the effective POM as an XML for this build, with the active profiles
factored in, or a specified artifact. If verbose, a comment is added to each
XML element describing the origin of the line.
help:help
Display help information on maven-help-plugin.
Call mvn help:help -Ddetail=true -Dgoal= to display parameter
details.
- 获取
maven-help-plugin
插件名称的简称
# 使用上面的describe目标查看这个插件的详情
E:\project\spring\mavenbase>mvn org.apache.maven.plugins:maven-help-plugin:describe -Dplugin=org.apache.maven.plugins:maven-help-plugin
...
[INFO] org.apache.maven.plugins:maven-help-plugin:3.2.0
Name: Apache Maven Help Plugin
...
Group Id: org.apache.maven.plugins
Artifact Id: maven-help-plugin
Version: 3.2.0
# 可以看出`maven-help-plugin`的简称是help,
Goal Prefix: help
- 查看
maven-clean-plugin
插件(简称:clean)的目标
# 命令中使用的是插件的简称
E:\my-project\spring\mavenbase>mvn help:describe -Dplugin=clean
...
Name: Maven Clean Plugin
...
Group Id: org.apache.maven.plugins
Artifact Id: maven-clean-plugin
Version: 2.5
# 这个插件的简称确实是clean
Goal Prefix: clean
# 这个插件有两个目标
This plugin has 2 goals:
clean:clean
Description: Goal which cleans the build.
This attempts to clean a project's working directory of the files that were
generated at build-time. By default, it discovers and deletes the
directories configured in project.build.directory,
project.build.outputDirectory, project.build.testOutputDirectory, and
project.reporting.outputDirectory.
Files outside the default may also be included in the deletion by
configuring the filesets tag.
clean:help
Description: Display help information on maven-clean-plugin.
Call
mvn clean:help -Ddetail=true -Dgoal=
to display parameter details.
- 查看
maven-clean-plugin
插件的clean目标详情
E:\project\spring\mavenbase>mvn help:describe -Dplugin=clean -Dgoal=clean -Ddetail
...
[INFO] Mojo: 'clean:clean'
clean:clean
Description: ...
# 这个目标开放了这些参数可设置,这也是我们在POM文件里通过可以指定的参数来源
Available parameters:
excludeDefaultDirectories (Default: false)
User property: clean.excludeDefaultDirectories
...
failOnError (Default: true)
User property: maven.clean.failOnError
...
filesets
The list of file sets to delete, in addition to the default directories.
...
followSymLinks (Default: false)
User property: clean.followSymLinks
...
retryOnError (Default: true)
User property: maven.clean.retryOnError
...
skip (Default: false)
User property: clean.skip
...
verbose
User property: clean.verbose
...
上面的目标详情里有两点值得注意:
- 目标开放的参数即是POM文件里可以通过
指定的参数 - 有些目标参数的说明里有
User property
,这个对应的即是POM文件里我们可以直接通过来指定的参数
看到这里后,读者应该明白了mvn clean package -DskipTests
命令里的参数为什么能生效了。(可以自行验证猜想)
2.3 验证插件目标与生命周期阶段的绑定关系
前面我们直接给出了maven中的一些常见生命周期阶段与插件目标的绑定关系,下面我们通过查看父POM来验证下。(每个POM文件都会显示或隐式的继承自一个父POM,如果我们没有在项目POM文件中显示的声明
E:\project\spring\mavenbase>mvn help:effective-pom
...
[INFO] --- maven-help-plugin:3.2.0:effective-pom (default-cli) @ maven-base ---
[INFO]
Effective POMs, after inheritance, interpolation, and profiles are applied:
4.0.0
com.gjj.maven
maven-base
1.0-SNAPSHOT
# 很多项目都会指定这个,经过今天的学习后,你应该能够知道这个为什么能够直接生效?
# 提示:读者可以去看一下maven-resources-plugin这个插件的目标及参数详情
UTF-8
# maven默认从这里去拉取依赖
false
central
Central Repository
https://repo.maven.apache.org/maven2
# maven默认从这里去拉取插件
never
false
central
Central Repository
https://repo.maven.apache.org/maven2
# maven默认的源代码、测试代码、编译代码的路径
E:\my-project\spring\mavenbase\src\main\java
E:\my-project\spring\mavenbase\src\main\scripts
E:\my-project\spring\mavenbase\src\test\java
E:\my-project\spring\mavenbase\target\classes
E:\my-project\spring\mavenbase\target\test-classes
E:\my-project\spring\mavenbase\src\main\resources
E:\my-project\spring\mavenbase\src\test\resources
E:\my-project\spring\mavenbase\target
maven-base-1.0-SNAPSHOT
maven-antrun-plugin
1.3
maven-assembly-plugin
2.2-beta-5
maven-dependency-plugin
2.8
maven-release-plugin
2.3.2
# 以下即是插件目标与生命周期阶段的默认绑定关系
maven-clean-plugin
2.5
default-clean
# maven-clean-plugin插件的goal=clean目标绑定到clean阶段
clean
clean
maven-resources-plugin
2.6
default-testResources
# maven-resources-plugin插件的goal=testResources目标绑定到process-test-resources阶段
process-test-resources
testResources
default-resources
# maven-resources-plugin插件的goal=resources目标绑定到process-resources阶段
process-resources
resources
maven-jar-plugin
2.4
default-jar
# maven-jar-plugin插件的goal=jar目标绑定到package阶段
package
jar
maven-compiler-plugin
3.1
default-compile
# maven-compiler-plugin插件的goal=compile目标绑定到compile阶段
compile
compile
default-testCompile
# maven-compiler-plugin插件的goal=testCompile目标绑定到test-compile阶段
test-compile
testCompile
maven-surefire-plugin
2.12.4
default-test
test
test
maven-install-plugin
2.4
default-install
install
install
maven-deploy-plugin
2.7
default-deploy
deploy
deploy
maven-site-plugin
3.3
default-site
site
site
E:\my-project\spring\mavenbase\target\site
org.apache.maven.plugins
maven-project-info-reports-plugin
default-deploy
site-deploy
deploy
E:\my-project\spring\mavenbase\target\site
org.apache.maven.plugins
maven-project-info-reports-plugin
E:\my-project\spring\mavenbase\target\site
org.apache.maven.plugins
maven-project-info-reports-plugin
E:\project\spring\mavenbase\target\site
3. 前文遗留的两个问题
- 什么时候需要指定依赖的scope标签?
- 什么时候需要将依赖指定为
scope=provided
:这个依赖我们在编译及测试过程中需要使用该依赖,但是在运行时会有其它服务器提供该依赖。如web应用中依赖servlet-api.jar,但是在运行时我们不需要该 jar包,因为这个jar 包已由web服务器提供,如果在打包时又被加入进去,那么就可能产生冲突。常见的如:servlet-api.jar、jsp-api.jar - 什么时候需要将依赖指定为
scope=runtime
:这个依赖仅在测试及运行时需要使用,但是在编译时没有也能通过编译。如mysql-connector-java.jar,编码期间不会使用该依赖,但是运行时需要使用该依赖操作数据库。 - 什么时候需要将依赖指定为
scope=test
:这个依赖仅被测试代码依赖,项目源代码的编译、运行均不需要该依赖。打包时这些依赖也会被忽略。
- 什么时候需要将依赖指定为
- 什么时候需要显示的指定type标签值?
- 什么时候需要指定
type=pom
:当所需要的依赖的打包方式为pom包时。 - 什么时候需要指定
type=war
:当所需要的依赖的打包方式为war包时。
- 什么时候需要指定
4. 参考
关于插件原理讲的很清楚:http://www.itsoku.com/article/240#menu_14
关于scope标签内容:https://blog.csdn.net/u010979642/article/details/107554068/