目录
一 介绍
构建一个项目通常由多个任务组成, 如下载依赖,放入classpath下,编译源码,运行测试,打包,部署等. 而maven则是一个自动化这些任务的工具.
Maven核心上是一个执行插件的框架,所有的工作都由插件完成。插件提供了很多goal,goal可以挂在在构建的不同生命周期中运行. 在super pom(见2.2.2小节)中提供了默认插件, 以提供maven基本功能.
生命周期是项目构建的主要方向, Maven有三个内置的生命周期:
- clean: 用于清理项目
- default: 编译部署项目
- site: 生成网页文档
一个生命周期由一系列phase(阶段)组成,当maven执行某个phase时,如mvn package
, 会按顺序执行之前的phase和该phase。实际上phase会被映射到底层的goals,也就是说,真正执行的是goals,并且一个phase可以执行多个goals。根据项目的不同打包类型,每个phase使用的goals可能不同。为了执行内置的生命周期,一些插件会默认被maven使用。
具体关于Lifecycle vs. Phase vs. Plugin vs. Goal的区别,请看参考链接
为了方便生成项目, maven提供了一堆模板供使用, 如mvn archetype:generate ...(一堆参数)
,archetype
是插件提供给这个goal的前缀.
maven有两种类型仓库: 依赖仓库和插件仓库. 从来源也可以将仓库分为本地和远程仓库, 当构建项目时, maven会先从本地查找依赖, 无则从远程仓库查找依赖.
maven项目有个固定的标准的目录结构, 也是super pom中设置好的.
maven项目的配置由pom文件提供, pom文件中所有元素清单如下:
<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">
<!-- pom版本, 必须 -->
<modelVersion>4.0.0</modelVersion>
<!-- The Basics -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<dependencies>...</dependencies>
<parent>...</parent>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<!-- Build Settings -->
<build>...</build>
<reporting>...</reporting>
<!-- More Project Information -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- Environment Settings -->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
二 基础
2.1 项目坐标
用于区分项目
- groupId:组织或公司唯一标识, 常使用倒置域名, 如
com.baidu
- artifactId:项目名, 即组织或公司开发的项目的名字, 如
my-project
- version:版本号。如
2.0
,2.0.1
,1.3.1
,而SNAPSHOT
表示正在开发.
这些参数仅用于标识项目, 不会对项目结构造成影响, 如package结构. 但会影响maven仓库中项目的存储结构, 如$M2_REPO/com/baidu/my-project/1.0
packaging
定义项目打包时生成文件的类型, 默认jar
, 所有可选值: pom
, jar
, maven-plugin
, ejb
, war
, ear
, rar
. packaging
的选择会影响整个生命周期中执行的goals
打包时, 默认会将依赖加入目标包中
2.2 POM关系
项目之间有依赖,继承,多模块关系. 如一个项目依赖另一个项目; 子项目继承父项目, 父项目多用于为子项目管理依赖; 多模块项目的顶层项目主要用于将多个模块(项目)分成一组, 只需对顶层项目构建, 所有模块都会被构建. 一般复杂项目中会同时存在这三种关系.
2.2.1 dependencies
一个简单的例子:
<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">
...
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<type>jar</type>
<scope>test</scope>
<optional>true</optional>
</dependency>
...
</dependencies>
...
</project>
-
groupId, artifactId, version: 三个坐标确定一个依赖.
-
type: 依赖类型, 默认
jar
-
scope: 指定依赖什么时候在classpath中可见, 不同类型的scope还会影响它的传递性.
scope 编译时 测试时 运行时 传递性 注意 compile(default) 提供 提供 提供 有 provided 提供 提供 否 无 一般JDK或容器已提供
如servlet-api
runtime 否 提供 提供 应该有 编译时不需要, 用于辅助
如spring-boot-devtools
test 否 提供 否 无 用于测试代码
如junit
system 提供 提供 否 无 类似 provided
, 但Jar在系统
其他地方提供, 需要配合systemPath
使用 -
optional: 直接影响依赖的传递性,
true
时无传递性, 默认false
-
systemPath: 仅当
scope
为system
时才生效, 指定该依赖的位置.
版本定义规则(了解)
1.0
: “Soft” requirement on 1.0 (just a recommendation, if it matches all other ranges for the dependency)[1.0]
: “Hard” requirement on 1.0(,1.0]
: x <= 1.0[1.2,1.3]
: 1.2 <= x <= 1.3[1.0,2.0)
: 1.0 <= x < 2.0[1.5,)
: x >= 1.5(,1.0],[1.2,)
: x <= 1.0 or x >= 1.2; multiple sets are comma-separated(,1.1),(1.1,)
: this excludes 1.1 (for example if it is known not to work in combination with this library)
还有什么版本顺序, 不知道啥子用, 见Version Order
排除
当传递依赖冲突时, 可以使用exclusion
元素, 排除某个依赖的传递依赖
<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">
...
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</exclusion>
</exclusions>
</dependency>
...
</dependencies>
...
</project>
或者使用*
排除某个依赖的所有依赖
<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">
...
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.1.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
...
</dependencies>
...
</project>
2.2.2 继承
pom之间是可以继承的, 子pom可继承的元素有:
- groupId
- version
- description
- url
- inceptionYear
- organization
- licenses
- developers
- contributors
- mailingLists
- scm
- issueManagement
- ciManagement
- properties
- dependencyManagement
- dependencies
- repositories
- pluginRepositories
- build
- plugin executions with matching ids
- plugin configuration
- etc.
- reporting
- profiles
不能继承的有:
- artifactId
- name
- prerequisites
比如, 子pom可以从父pom中继承依赖(dependencies
).
每个pom都有父pom, 没有声明时会隐式继承Super POM, 从中可以看到pom的一些默认设置, 如默认的标准项目结构, 默认运行所必须的插件等.
使用步骤如下:
-
父pom声明
packaging
元素为pom
<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>org.codehaus.mojo</groupId> <artifactId>my-parent</artifactId> <version>2.0</version> <packaging>pom</packaging> </project>
-
子pom中声明父pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.codehaus.mojo</groupId> <artifactId>my-parent</artifactId> <version>2.0</version> <relativePath>../my-parent</relativePath> </parent> <artifactId>my-project</artifactId> </project>
relativePath
指定父pom位置, 默认../pom.xml
子pom查询父pom的顺序:
relativePath
–>本地仓库–>远程仓库
dependencyManagement
通过该元素可以管理所有子pom的依赖信息. 它和dependencies
不同, 子pom会从父pom中继承dependencies
声明的依赖, 而至于dependencyManagement
, 则子pom中存在dependencyManagement
的依赖时, 会从该元素中获取信息, 如版本号(version
)和作用于(scope
), 因此子pom中可以省略这些信息. 在spring boot项目中该用法十分常见.
2.2.3 多模块
多模块项目(也叫Aggregation项目), 由多个模块组成, 每个模块都是一个完整的项目. 这样的好处是将多个项目当作一个组, 执行maven生命周期时, 如打包, 所有的项目都会被打包 , 并且maven会处理好不同项目之间的依赖关系而选择正确的打包顺序, 如先打包dao项目, 后service项目等等.
使用: 首先多模块项目的packaging
声明为pom
, 然后添加modules
元素, 如下所示:
<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>org.codehaus.mojo</groupId>
<artifactId>my-parent</artifactId>
<version>2.0</version>
<packaging>pom</packaging>
<modules>
<module>my-project</module>
<module>another-project</module>
<module>third-project/pom-example.xml</module>
</modules>
</project>
module
元素中填项目的相对路径或这些项目的pom文件地址
关于继承和多模块
通常继承和多模块会同时存在于一个顶层项目中, 但是他们没有必然关系, 即被继承的父pom项目可以不存在多模块, 多模块项目可以不被子模块继承.
2.3 属性(Porperties)
属性是一个占位符, 可以在pom中其他地方使用, 如:
<project>
...
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
...
</project>
然后可以通过${x}
来使用它, 如${maven.compiler.source}
三 生命周期
三个生命周期所有的phase如下所示:
- clean:pre-clean, clean, post-clean
- default: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 - site:pre-site, site, post-site, site-deploy
default lifecycle常用的phases:
所有常用的phases如下(来自idea截图):
四 其他
4.1 父、子POM(废弃)
该小节冗余, 将废弃, 但是不忍心删除这些内容
一个项目可以由多个相对独立的模块(小项目)组成,每个模块可以存在一个pom文件。为了防止模块之间冗余,父pom抽离子pom间的公共部分,由子pom继承父pom配置,使项目更容易维护。
当父、子pom中属性或依赖冲突时,子pom优先级高。
4.1.1 定义
-
父pom:通过在pom中定义
<packaging>pom</packaging>
来声明; -
子pom:pom中添加
parent
元素,如:<parent> <groupId>top.sidian123.demo</groupId> <artifactId>MavenExamples</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
parent
元素中可定义relativePath
元素,表明父pom相对于当前子pom的位置。默认../pom.xml
,如果不填,则从仓库中查找。
4.1.2 依赖查找
- 先在父pom中查找依赖
- 在本地仓库中查找
- 最后查找远程仓库
4.1.3 dependencyManagement
dependencyManagement
用在父pom文件中,只有子pom中存在dependencyManagement
中的依赖,才会从该依赖中继承未指定的配置,如spring boot中的版本号。
4.2 资源插件
maven默认资源插件负责将项目中资源拷贝到输出目录中。默认资源放在src/main/resources
中。
-
更改默认资源文件夹:
<build> ... <resources> <resource> <directory>src/my-resources</directory> </resource> </resources> ... </build>
-
多个资源目录
<build> ... <resources> <resource> <directory>resource1</directory> </resource> <resource> <directory>resource2</directory> </resource> <resource> <directory>resource3</directory> </resource> </resources> ... </build>
-
仅引入目录中匹配成功的资源:
<build> ... <resources> <resource> <directory>[your directory]</directory> <includes> <include>**/*.txt</include> <include>**/*.rtf</include> </includes> </resource> ... </resources> ... </build>
-
引入目录中所有资源,除了匹配成功的:
<build> ... <resources> <resource> <directory>src/my-resources</directory> <excludes> <exclude>**/*.bmp</exclude> <exclude>**/*.jpg</exclude> <exclude>**/*.jpeg</exclude> <exclude>**/*.gif</exclude> </excludes> </resource> ... </resources> ... </build>
-
对
<includes>
引入的资源用<excludes>
筛选:<build> ... <resources> <resource> <directory>src/my-resources</directory> <includes> <include>**/*.txt</include> </includes> <excludes> <exclude>**/*test*.*</exclude> </excludes> </resource> ... </resources> ... </build>
4.3 插件
总的,插件可以被归为两类:
- Build plugins:在项目构建时执行;在pom的
<build/>
元素中配置 - Reporting plugins:在生成文档时被执行;在pomr的
<reporting/>
元素中配置
4.4 安装
-
首先安装了JDK, 并且设置环境变量
JAVA_HOME
export JAVA_HOME=/home/sidian/Software/jdk-11.0.3
-
官网上下载最新版Maven并解压
-
将Maven的
bin
目录添加到环境变量中. -
运行
mvn --version
进行测试
五 依赖进阶
- 项目依赖除了来源于pom文件中声明的依赖外, 还有从父依赖继承的依赖, 依赖的传递依赖. 因此这里将依赖区分为: 直接依赖, 继承依赖, 传递依赖
- 依赖间可能会出现依赖版本冲突的问题.
5.1 冲突解决
-
最近选择: 当传递依赖冲突时, 依赖树中离项目最近的依赖的版本被选择
最终会使用1.0版本的D依赖
我们也可以声明直接依赖来解决冲突问题.
-
依赖管理(见2.2.2小节): 依赖管理除了可以指定未声明版本的直接依赖的版本外, 传递依赖的版本可以通过
dependencyManagement
强制确定.注意, 该项目的直接依赖和继承依赖不能被依赖管理影响.
-
排除依赖(见2.2.1小节): 通过
exclusion
元素可以排除依赖, 如A->B->D, 可以在A中排除D依赖. -
依赖作用域(
scope
): 声明直接依赖时可以指定它的作用域,scope
不仅影响该依赖, 还影响该依赖的传递依赖的有效作用域. 具体规则如下所示:compile provided runtime test compile compile(*) - runtime - provided provided - provided - runtime runtime - runtime - test test - test - 行表示依赖的
scope
, 列表示传递依赖的scope
, 值为传递依赖的有效作用域设置直接依赖的不同
scope
, 来影响传递依赖的有效scope
来达到解决冲突的目录, 但是因为不太直观, 因此不建议使用该方法.
5.2 引入版本管理
dependencyManagement
元素主要用于管理依赖的版本, 以至于声明依赖时不用写版本号. 通过我们会通过父pom来集中管理依赖, 然后子pom通过继承来获取该元素.
当有多个版本管理pom文件需要引入时会出现问题, 因为父pom只能存在一个. 可以通过scope
元素的import
值引入, 如下所示:
project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>baeldung</groupId>
<artifactId>Test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Test</name>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>baeldung</groupId>
<artifactId>Baeldung-BOM</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
import
只能在dependencyManagement
元素使用, 并且引入的依赖为pom
类型. 上面的例子中引入了Baeldung-BOM
的dependencyManagement
元素来进行依赖管理.
这种方法还是和继承有区别的, import
方法仅"继承"dependencyManagement
元素.
5.3 有效依赖版本
当依赖在多个地方声明版本时, 到低使用哪个版本呢? 下面给出答案, 优先级由高到低:
- 声明直接依赖时指定的版本号
- 继承依赖的版本号
- 引入(
import
)pom的版本号, 也考虑引入pom间的顺序 - 传递依赖的最近选择算法确定(见5.1)