Maven 核心概念
POM 文件
POM(Project Object Model) 全名为项目对象模型,定义了项目的基本信息,用于描述项目如何构建、声明项目依赖等等。
项目坐标
<?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.cqeips.springcloud</groupId>
<artifactId>springcloud-starter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>springcloud-starter</name>
<description>Spring cloud starter</description>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
- xml 定义 XML 文件的版本和编码方式
- project pom 文件的根元素,以及命名空间等
- modelVersion pom 模型的版本,Maven2 、Maven3 来说,必须为4.0.0
- groupId 项目组,通常采用反域名格式,一般不定义到组织级别,应该定义到项目级别,如
com.cqeips.springcloud
- artifactId 当前Maven项目组中唯一ID
- version 当前版本号
- packaging 打包方式,默认为 jar
- name 项目名称
- description 项目描述
其中 groupId、artifactId、version 是必选内容,这三个参数确定了项目的位置坐标,其他参数可选。
dependencies
项目依赖 <dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>...</exclusion>
</exclusions>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
- dependencies 该元素描述了项目相关的所有依赖。可以包含一个或多个dependecy。
- dependency 单个依赖
- type 依赖类型,对应项目坐标定义的packaging。
- scope 依赖范围。
- optional 依赖是否可选。
- exclusions 用于排除传递性依赖
scope
依赖范围用来控制依赖与classpath(编译classpath、测试classpath、运行classpath)的关系,maven的有以下几种依赖范围:
- complile: 编译依赖范围。默认使用该依赖范围,对于编译、运行、测试三种classpath都有效。如:spring-core。
- test: 测试依赖范围。只对于测试classpath有效。如:junit。
- provided: 已提供依赖范围。对于编译和测试classpath有效,对于运行时无效。在运行时一般需要容器进行提供,不需要Maven重复引入。如sevlet-api。
- runtime: 运行时依赖范围。对于测试和运行classpath有效,对于编译主代码时无效。如JDBC驱动实现。
- system: 系统依赖范围。对于编译和测试classpath有效,对于运行时无效。需要通过
systemPath
显示指定依赖文件的路径。由于此依赖范围不是由Maven仓库解析的,而是与本机文件系统绑定,可能造成构建的不可移植,需谨慎使用。systemPath
元素可以引用环境变量,如:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
2
3
4
5
6
7
- import: 导入依赖范围。该依赖不会对三种classpath产生影响。
传递性依赖
所谓传递性依赖指所依赖的包间接产生的依赖。如引入spring-core
,会间接引入common-logging
包。传递性依赖范围由直接依赖和间接依赖范围共同决定,依赖关系如下表所示:
左边为直接依赖,顶部为间接依赖。
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | —— | —— | runtime |
test | test | —— | —— | test |
provided | provided | —— | provided | provided |
runtime | runtime | —— | —— | runtime |
依赖调节
如果在项目中引入多个依赖包,可能造成传递性依赖问题。这时Maven遵循以下两个原则:
- 路径最短原则
如有两条依赖路径:- A>B>C>X(1.0)
- A>D>X(2.0) 则X(2.0)优先解析使用。
- 顺序靠前原则
当路径长度相同时,谁靠前引入,谁优先使用。
可选依赖
不推荐使用! 某一个项目B实现了多个特性,其中特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不可能同时选择这两个特性。如果项目A依赖于项目B,则X、Y对于项目A来说则为可选依赖。
排除依赖
看下面的例子
<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>project-a</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>project-b</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>project-c</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>project-c</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在项目A中引入项目B依赖,项目B又依赖于项目C,由于某种原因不想引入传递性依赖C,而是显示指定项目C的版本,这时可以使用exclusion
排除传递性依赖C。
A------->B------>C(被排除)
|------->C(显示指定版本)
2
归类依赖
在引入依赖库时,我们可以将依赖的版本单独提取出来,进行统一管理,方面后续修改或升级。这是就需要使用properties
元素。如:
<properties>
<springframework.version>2.5.6</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Maven 仓库
仓库的分类
Maven仓库分为本地仓库和远程仓库。当引入一个依赖时,首先会在本地仓库进行查找,如果本地仓库不存在,则会访问远程仓库进行查找,同时下载到本地仓库。如果远程仓库也不存在该依赖,则报错。其中远程仓库又可以分为中央仓库、私服和其他仓库。
Maven仓库
|
-----------------------------
| |
本地仓库 远程仓库
|
-------------------------------------
| | |
中央仓库 私服 其他仓库
2
3
4
5
6
7
8
9
本地仓库
默认情况下,Maven 默认仓库地址为~/.m2/repository
,如果用户希望自定义本地仓库地址,可以通过修改~/.m2/settings.xml
进行设置,修改localRepository
元素为希望的地址:
<settings>
<localRepository>D:\java\repository\</localRepositry>
</settings>
2
3
中央仓库
Maven至少必须配置一个可用的远程仓库,中央仓库就是这样一个默认的远程仓库。默认中央仓库的配置在%MAVEN_HOME%\lib\maven-model-builder-3.5.0.jar
中:
<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>
2
3
4
5
6
7
8
9
10
11
私服
配置远程仓库
在某些情况下,默认的中央仓库无法满足项目需求,需要配置额外的远程仓库。这时需要在POM文件中进行仓库配置:
<repositories>
<repository>
<id>...</id>
<name>...</name>
<url>...</url>
<release>
<enable>true</enable>
<updatePolicy>daily|never|always|interval:X</updatePolicy>
<checkSumPolicy>...</checkSumPolicy>
</release>
<snapshots>
<enable>false</enable>
</snapshots>
<layout>default</layout>
</repository>
</repositories>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- id 仓库的唯一ID,相同的ID,仓库的配置会被覆盖
- name 仓库名称
- url 仓库地址
- release 发布版本,enable为true表示开启发布版本下载
- snapshot 快照版本,enable为true表示开启快照版本下载
- layout 仓库布局,默认为Maven2或Maven3布局
- updatePolicy 更新策略,daily每天更新;never从不更新;always每次构建都检查更新;interval:X 每X分钟检查更新
- checkSumPolicy 检查校验和文件策略,warn/fail/ignore
远程仓库认证
有时为了安全方面考虑需要限制远程仓库的访问权限,需要对远程仓库配置认证信息。认证信息配置在setting.xml
中:
<settings>
...
<servers>
<id>...</id>
<username>...</username>
<password>...</password>
</servers>
</settings>
2
3
4
5
6
7
8
setting.xml
中server
元素id 必须和 POM 文件中需要认证的repository
保持一致。
部署至远程仓库
有时需要将第三方构建或内部构建部署到远程仓库中,需要对POM文件进行配置:
<distributionManagement>
<repository>
<uniqueVersion />
<id> banseon-maven2 </id>
<name> banseon maven2 </name>
<url> file://${basedir}/target/deploy </url>
<layout></layout>
</repository>
<snapshotRepository>
<uniqueVersion />
<id> banseon-maven2 </id>
<name> Banseon-maven2 Snapshot Repository </name>
<url> scp://svn.baidu.com/banseon:/usr/local/maven-snapshot </url>
<layout></layout>
</snapshotRepository>
</distributionManagement>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
配置正确后在命令行运行mvn clean deploy
进行部署。
配置仓库镜像
仓库镜像往往比中央仓库提供更快速、稳定的服务。比如阿里云镜像。配置镜像仓库代替中央仓库,修改settings.xml
:
<settings>
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
</settings>
2
3
4
5
6
7
8
9
10
该配置中mirrorOf
值为central
表示使用镜像仓库代替中央仓库,任何对于中央仓库的请求都会转到该仓库。mirrorOf
值为*
表示该镜像为所有仓库的镜像。
更多阿里云镜像请参考AliRepo
Maven 支持更高级的镜像配置:
<mirrorOf>*</mirrorOf>
匹配所有远程仓库<mirrorOf>external:*</mirrorOf>
匹配所有远程仓库。使用localhost
、file://
协议除外。匹配所有非本机远程仓库。<mirrorOf>repo1、repo2</mirrorOf>
匹配repo1和repo2,多个仓库使用,
分割<mirrorOf>*,!repo1</mirrorOf>
匹配所有远程仓库,repo1除外。
生命周期
Maven 拥有三套独立的生命周期,分别为clean
、default
、site
,每套生命周期相互独立互不干扰。每套生命周期又分为多个阶段(phase),这些phase
是有顺序的,后面的阶段依赖于前面的阶段。详见Maven Lifecycle Reference
clean
生命周期
用于清理项目,包含三个阶段:
pre-clean
执行清理前需要完成的工作clean
清理上次构建生成的文件post-clean
执行清理后需要完成的工作
default
生命周期
定义了构建时需要执行的所有步骤,其中包括的主要阶段如下:
validate
initialize
generate-sources
process-sources
处理项目主资源文件。一般来说指将src/main/resources
目录内的内容进行变量替换等工作后,复制到项目输出的主classpath
目录中。generate-resources
process-resources
compile
编译项目的主源码。一般来说,是编译src/main/java
目录下的Java文件至项目输出的主classpath
目录中。process-classes
处理项目测试资源文件。一般来说,是对src/main/resources
目录内容进行变量替换等工作后,复制到项目输出的测试classpath
目录中。generate-test-sources
process-test-sources
generate-test-resources
process-test-resources
test-compile
编译项目的测试代码。一般来说,是编译src/test/java
目录下的Java文件至项目输出的测试classpath
目录中。process-test-classes
test
使用单元测试框架运行测试,测试代码不会被打包或部署。prepare-package
package
接受编译好的代码,打包成可发布的格式,如JAR。pre-integration-test
integration-test
post-integration-test
verify
install
将包安装到Maven本地仓库,供本地其他Maven项目使用。deploy
将最终的包部署到远程仓库,供其他开发人员和Maven项目使用。
site
生命周期
用于建立和发布项目站点,主要生命周期如下:
pre-site
执行一些在项目站点之前需要完成的工作。site
生成项目站点文档。post-site
执行一些在生成项目站点之后需要完成的工作。site-deploy
将生成的项目站点发布到服务器。
命令行与生命周期
mvn clean
调用clean
生命周期的clean
阶段。mvn test
调用default
生命周期的test
阶段。实际执行会从validate
阶段一直执行到test
阶段。mvn clean install
调用clean
生命周期clean
阶段和default
生命周期install
阶段。mvn clean deploy site-deploy
调用clean
生命周期clean
阶段,default
生命周期deploy
阶段,site
生命周期site-deploy
阶段
插件
插件往往与生命周期相互绑定,用于完成生命周期中定义的具体任务,插件以独立的构件形式存在。
插件目标
对于一个插件而言,往往拥有很多个功能,这些功能能够完成多个任务。如maven-dependency-plugin
,能够分析依赖、列出项目依赖树、列出项目所有已解析的依赖等等。这些功能同时聚合在一个插件中,每个功能就是一个插件目标。上述几个功能对应的插件目标为:dependency:analyze
、dependency:tree
、dependency:list
,这种通用写法格式为:(插件前缀):(插件目标)。
插件绑定
插件需要与生命周期的各阶段绑定才能正常工作,插件的绑定分为内置绑定和自定义绑定两种,下面分别看看这两种绑定。
内置绑定
Maven生命周期的各阶段默认绑定了很多插件,当调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。
clean
生命周期内置绑定
生命周期 | 插件目标 |
---|---|
pre-clean | maven-clean-plugin:clean |
clean | |
post-clean |
site
生命周期内置绑定
生命周期 | 插件目标 |
---|---|
pre-site | maven-site-plugin:site |
site | |
post-site | |
site-deploy | maven-site-plugin:deploy |
default
生命周期内置绑定default
生命周期的内置绑定相对于clean
、site
来说比较复杂。项目的打包类型会影响构建的过程,因此,default
生命周期的阶段与插件目标的绑定关系由项目打包类型所决定。最常见的为jar
打包类型,内置插件绑定关系如下:
生命周期 | 插件目标 |
---|---|
process-resources | maven-resources-plugin:resources |
compile | maven-compiler-plugin:compile |
process-test-resources | maven-resources-plugin:test-resources |
test-compile | maven-compiler-plugin:test-compile |
test | maven-surefire-plugin:test |
package | maven-jar-plugin:jar |
install | maven-install-plugin:install |
deploy | maven-deploy-plugin:deploy |
对于以上的内容仅列出其中部分内置依赖关系,详细内容请参考Maven Built-in Lifecycle Bindings
自定义绑定
除了内置绑定外,用户还可以将某个插件目标绑定到生命周期的某个阶段。如下:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<pahse>verify</pahse>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
executions
配置多个执行任务execution
配置单个执行任务id
任务IDphase
配置生命周期阶段,将任务与阶段进行绑定goals
指定要执行的插件目标
很多插件目标在编写时已经定义了默认绑定的阶段,因此可以不用配置phase
元素,可以通过maven-help-plugin
查看更多信息。
插件配置
完成插件与生命周期绑定后,还可以配置插件目标的参数,进一步调整插件目标执行的任务。通常有三种插件配置方式:
- 命令行插件参数配置
我们可以在命令行中使用
-D
携带参数信息来配置插件目标参数,如:mvn install -Dmaven.test.skip=true
,maven-surefire-plugin
中提供了一个maven.test.skip
参数,当值为true
的时候,就会跳过测试。 - POM中插件全局配置 在声明插件的时候,对插件进行全局配置,此插件的所有目标任务都会使用这些配置。如:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2
3
4
5
6
7
8
9
10
11
12
13
- POM中插件任务配置 除了进行全局配置,用户可以为某个特定插件任务配置参数。如:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
<excutions>
<excution>
<id>ant-validate</id>
<phase>validate</phase>
<goals>
<goal>run<goal>
</goals>
<configuration>
<tasks>
<echo>I'm bound to validate phase.</echo>
</tasks>
</configuration>
</excution>
</excutions>
</plugin>
</plugins>
</build>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
聚合与继承
聚合
在多模块下,Maven可以通过聚合模块的方式实现多模块的构建、打包等工作。聚合模块的POM文件中packaging
值必须为pom
,否则无法完成构建。通常聚合模块的POM文件格式如下:
...
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>pom</packaging>
<name>...</name>
<modules>
<module>module-a</moudule>
<module>module-b</module>
</modules>
2
3
4
5
6
7
8
9
10
- modules 用于引入多模块配置
- module 子模块POM文件的相对目录
通常,模块所在的目录名称应当与其artifactId一致,如果用户将module-a项目放到a-module目录下,这时聚合模块的配置就要相应改成<module>a-module</module>
。
继承
POM的继承,用于将子模块POM文件中相同的公共配置提取到聚合模块中。
- 首先提取出公共或重复配置到父模块系统中,如下:
<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>module-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Module Parent</name>
2
3
4
5
- 配置子模块继承父模块,如下:
<parent>
<groupId>xyz.yxguang.mvn.demo</groupId>
<artifactId>module-parent</artifactId>
<version>1.0.0</version>
<relativePath></relativePath>
</parent>
<artifactId>module-a</artifactId>
<name>module-a</name>
2
3
4
5
6
7
8
- relativePath 表示父元素POM文件的相对路径
- 聚合模块,如下
<modules>
<module>module-a</module>
<module>module-b</module>
</modules>
2
3
4
可继承的POM元素
- groupId
- version
- description
- orgnaization
- inceptionYear
- url
- developers
- contributors
- distributionManagement
- issueManagement
- ciManagement
- scm
- mailingLists
- properties
- dependencies
- dependencyManagement
- repositories
- build
- reporting
依赖管理
dependenceis 与 dependencyManagement
dependencies
支持依赖继承,如果子模块继承父模块,则子模块继承父模块所有dependencies
依赖。如:
module-parent 有四个依赖 dependency-1、dependency-2、dependency-3、dependency-4,如果module-a 继承module-parent 则,module-a 同时也会继承上述四个依赖。
上述做法可行,但存在问题。如果现在有一个新模块module-b需要继承module-parent,但是仅需要继承dependency-1 依赖,那么使用dependencies
无法满足我们的要求。dependencyManagement
dependencyManagement
能够让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。dependencyManagement
元素下的依赖声明不会引入实际的依赖,但是它能够管理子模块的依赖。