Maven 简介
Maven 是一款基于 Java 平台的项目构建,依赖管理的工具,使用 Maven 可以自动化构建,测试,打包和发布项目,大大提高了程序员的开发效率。
依赖管理
Maven 可以管理项目的依赖,包括自动下载依赖库,自动下载依赖需要的依赖并保证版本没有冲突,依赖版本管理等。
构建管理
项目构建指将源代码,配置文件,资源文件等转化为能够运行或部署的应用程序或库的过程。
Maven 可以管理项目的编译、测试、打包、部署等构建过程。通过实现标准的生命周期,Maven 可以确保每一个构建过程都遵循同样的规则和最佳实践。同时,Maven 的插件机制也使得开发者可以对构建过程进行扩展和定制。
Maven 原理
Maven 坐标
Maven 中的坐标(GAVP) 是指 GroupId、ArtifactId、Version、Packaging 等四个属性缩写,其中 GAV 是必要的,Packaging 是可选的。
GAV 遵循以下规则:
- GroupId:com.company.业务线.子也无线,最多四级
- ArtifactId:产品线名-模块名,不重复
- Version:主版本号.次版本号.修订号
- Packaging:Maven 项目的打包方式,默认 jar。通过 packaging 配置来打包,packaging = jar时,打成 jar 包;packaging=war,打成 war 包;packaging=pom 时,代表不会打包,用来做项目聚合或父工程。
Maven 依赖
依赖配置
pom 文件中跟元素 project 下的 dependencies 可以包含一个或多个dependency 元素来声明多个项目依赖。每个依赖包含的元素如下:
- groupId、artifactId、version:依赖的基本坐标
- type:依赖的类型,对应于项目坐标定义的 packaging。大部分情况不用声明,默认值 jar
- scope:依赖范围
- optional:标记依赖是否可选
- exclusions:排除传递性依赖
依赖配置示例
<project>
...
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<!--如下可选-->
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
<groupId>...</groupId>
<artifactId>...</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
依赖范围
依赖范围就是用来控制依赖和三种 classpath(编译 classpath、测试 classpath 和运行 classpath)的关系。
Maven 有以下几种依赖范围
- compile:编译依赖范围,默认的依赖范围,对编译、测试和运行三种 classpath 均有效。
- test:测试依赖范围。只对测试 classpath 有效
- provided:已提供依赖范围,对编译和测试 classpath 有效
- runtime:运行时依赖范围,对测试和运行 classpath 有效
- system:系统依赖范围,和 provided 依赖范围完全一致。但使用 system 时必须通过 systemPath 元素显示指定依赖文件的路径
- import:导入依赖范围,该依赖不会对三种 classpath 产生实际影响。
上述除 import 外的依赖范围与三种 classpath 的关系如下表
| 依赖范围
(Scope) | 对于编译 classpath 有效 | 对于测试 classpath 有效 | 对于运行 classpath 有效 | 示例 |
---|---|---|---|---|
compile | Y | Y | Y | spring-core |
test | - | Y | - | Junit |
provided | Y | Y | - | servlet-api |
runtime | - | Y | Y | JDBC驱动实现 |
system | Y | Y | - | 本地的,Maven 仓库之外的类库文件 |
依赖范围和传递性依赖
Maven 的传递性依赖就是解析直接依赖的 POM ,将那些必要的间接依赖,以传递性依赖的形式引入到当前项目中。
依赖范围不仅可以控制依赖和三种 classpath 的关系,还对传递性依赖产生影响。如下表
第二直接依赖 | |||||
---|---|---|---|---|---|
compile | test | provided | runtime | ||
第一直接依赖 | compile | compile | - | - | runtime |
test | test | - | - | test | |
provided | provided | - | provided | provided | |
runtime | runtime | - | - | runtime |
规律如下:
依赖调解
传递性依赖一方面大大简化了依赖声明,大部分情况下我们只关心直接依赖,不用考虑传递性依赖。但有时候传递性依赖也会造成问题,我们就需要清楚知道传递性依赖是从哪条依赖路径引入的。
Maven 依赖调解(Dependency Mediation)有两个原则。
- 原则一:路径最近者优先
- 原则二:第一声明优先
原则一说明:A -> B -> X(1.0),A -> D -> E -> X(2.0),X 是 A 的传递性依赖,但两条路径上有两个版本 X。Maven 会根据第一原则进行调解,X(1.0)的路径长度是 2,X(2.0) 的路径长度是 3,因此 X(1.0) 会被解析使用。
原则二说明:A -> B -> Y(1.0)、A -> C -> Y(2.0),Y 的路径都是 2,Maven 会根据在 POM 中依赖声明的顺序决定谁会被解析使用。
排除依赖
通过 Maven 的依赖调解后,还可能存在引入多个版本的依赖。此时可以通过手动配置的方式来进行依赖排除、
<dependency>
<groupId>com.hongguo.maven</groupId>
<artifactId>pro01-maven-java</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
<!-- 使用excludes标签配置依赖的排除 -->
<exclusions>
<!-- 在exclude标签中配置一个具体的排除 -->
<exclusion>
<!-- 指定要排除的依赖的坐标(不需要写version) -->
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
Maven 生命周期和插件
Maven 的生命周期是抽象的,本身不做任何实际工作,实际的任务都是交由插件来完成的。
三套生命周期
Maven 拥有三套生命周期,分别是 clean、default 和 site。官网文档
- clean:目的是清理项目
- default:目的是构建项目
- site:目的是建立项目站点
clean生命周期
clean 生命周期是清理项目,包括三个阶段:
- pre-clean:清理前需要完成的工作
- clean:清理构建生成的文件
- post-clean:清理后需要完成的工作
default生命周期
default 生命周期定义了构建时所执行的所有步骤,包含如下阶段:
- validate
- initialize
- generate-sources
- process-sources:处理项目主资源文件,一般来说,是对 src/main/resources 目录的内容进行变量替换等工作后,复制到项目输出的主 classpath 目录中
- generate-resources
- process-resources
- compile:编译项目的主源码。一般来说,是编译 src/main/java 目录下的 java 文件至项目输出的主 classpath 目录中
- process-classes
- generate-test-sources
- process-test-sources:处理项目测试资源文件,一般来说,是对 src/ test/resources 目录的内容进行变量替换等工作后,复制到项目输出的测试 classpath 目录中。
- generate-test-resources
- process-test-resources
- test-compile:编译项目的测试代码,一般来说,是编译 src/test/java 目录下的 java 文件至项目输出的测试 classpath 目录中
- process-test-classes
- test:使用单元测试框架进行测试,测试代码不会被打包或部署
- prepare-package
- package:接受编译好的代码,打包成可发布的格式,如 JAR 或 WAR
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install:将包安装到 Maven 本地仓库,供本地其他 Maven 项目使用
- deploy:将最终的包复制到远程仓库,供其他开发人员和 Maven 项目使用
site生命周期
site 生命周期目的是建立和发布项目站点,Maven 基于 POM 所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息,包含如下阶段:
- pre-site:执行生成站点之前需要完成的工作
- site:生成项目的站点文档
- post-site:执行生成项目站点之后需要完成的工作
- site-deploy:将生成的项目站点发布到服务器上
命令行和生命周期
从命令行执行 Maven 任务的最主要方式就是调用 Maven 的生命周期阶段
mvn clean:该命令调用 clean 生命周期的 clean 阶段。实际执行阶段为 clean 生命周期的 pre-clean 和 clean。
mvn test:该命令调用 default 生命周期的 test 阶段。实际执行的阶段为 default 生命周期的 validate、initialize 等,直到 test 的所有阶段。
mvn clean install:该命令调用 clean 生命周期的 clean 阶段和 default 生命周期的 install 阶段。实际执行的阶段为 clean 生命周期的 pre-clean、clean 阶段,以及 default 生命周期的从 validate 至 install 的所有阶段。
插件
插件目标
对于插件本身,为了能够复用代码,因此将多个功能聚集到一个插件里,每个功能就是一个插件目标。执行插件的命令通用写法为:插件名:目标名,例如:compile:compile(maven-compiler-plugin 的 compile 目标)
插件绑定
Maven 的生命周期与插件互相绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务。如下图:生命周期阶段与插件目标绑定
内置绑定
Maven 的核心的生命周期阶段绑定了很多插件的目标。
clean 生命周期与插件目标绑定关系
生命周期阶段 | 插件目标 |
---|---|
pre-clean | |
clean | maven-clean-plugin:clean |
post-clean |
site生命周期与插件目标绑定关系
生命周期阶段 | 插件目标 |
---|---|
pre-site | |
site | maven-site-plugin:site |
post-site | maven-site-plugin:deploy |
site-deploy |
default生命周期与插件目标绑定关系
生命周期阶段 | 插件目标 | 执行任务 |
---|---|---|
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-surefire-plugin:test | 执行测试用例 |
package | maven-jar-plugin:jar | 创建项目 jar 包 |
install | maven-install-plugin:install | 将项目输出构件安装到本地仓库 |
deploy | maven-deploy-plugin:deploy | 将项目输出构件部署到远程仓库 |
help 插件可以查看插件目标默认绑定的生命周期阶段
$mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin -Ddetail
......
source:jar-no-fork
Description: This goal bundles all the sources into a jar archive. This
goal functions the same as the jar goal but does not fork the build and is
suitable for attaching to the build lifecycle.
Implementation: org.apache.maven.plugins.source.SourceJarNoForkMojo
Language: java
Bound to phase: package
......
自定义绑定
除了内置绑定之外,用户可以选择将某个插件目标绑定到生命周期的某个阶段上。示例:创建项目的源码 jar 包,可以将 maven-source-plugin 插件的目标 jar-no-fork 绑定到default 的生命周期的 verify 阶段上。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>...</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
maven-help-plugin 插件,官网
- 重点的目标(待细化)
- help:describe
- help:evaluate
聚合和继承
聚合
概念
Maven 聚合是指将多个项目组织到一个父级项目中,以便一起构建和管理的机制。
作用
- 管理多个子项目
- 构建和发布一组相关的项目
- 优化构建顺序
- 统一管理依赖项
语法
父项目中包含子项目列表
<project>
<groupId>com.hongguo.maven</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<modules>
<!-- 引入子项目 -->
<module>child-project1</module>
<module>child-project2</module>
</modules>
</project>
继承
概念
Maven 继承是指在 Maven 的项目中,让一个项目从另一个项目中继承配置信息的机制
作用
- 父工程中统一管理项目中的信息,包括但不限于依赖信息,版本信息等 POM 中可定义的所有配置
语法
- 父工程
<project>
<groupId>com.hongguo.maven</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<!-- 父工程打包方式为 pom -->
<packaging>pom</packaging>
</project>
- 子工程
<project>
<parent>
<!-- 父工程坐标 -->
<groupId>com.hongguo.maven</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<!-- 子工程坐标 -->
<!-- 子工程坐标中 groupId 和 version 与父工程一致,则可以省略 -->
<!-- <groupId>com.hongguo.maven</groupId> -->
<artifactId>chile-project</artifactId>
<!-- <version>1.0.0</version> -->
</project>
父工程依赖统一管理
- 父工程声明依赖
<project>
<properties>
<spring-core.version>6.0.10</spring-core.version>
</properties>
<!-- 使用dependencyManagement标签配置对依赖的管理 -->
<!-- 被管理的依赖并没有真正被引入到工程 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-core.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
- 子工程引入依赖
<project>
<!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。 -->
<!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 -->
<!-- 具体来说是由父工程的dependencyManagement来决定。 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
</dependencies>
</project>
约定优于配置
原因之一是,使用约定可以大量减少配置。
版本管理
Maven的版本号约定
Maven 的版本号定义约定如下:
<主版本号>.<次版本号>.<增量版本号>-<里程碑版本号>
- 主版本号:表示项目的重大架构变更
- 次版本号:表示较大范围的功能增加和变化,以及 Bug 修复
- 增量版本号:一般表示重大 Bug 的修复
- 里程碑版本号:指某一个版本的里程碑
主干、标签和分支
- 主干:开发代码主体
- 分支:从主干上某个点分离出来的代码
- 标签:用来标识主干或分支的某个点状态
三者之间的关系如下:
Maven 灵活构建
Maven 属性
POM 中通过元素进行自定义Maven 属性,在 POM 中其他地方使用**${属性名称}**方式引用该属性。
Maven 中包含六类属性
- 内置属性:主要有两个常用内置属性
- ${basedir}:项目根目录
- ${version}:项目版本
- POM 属性:指 POM 文件中对应元素的值
- ${project.artifactId}
- ${project.build.sourceDirectory}
- ${project.build.testSourceDirectory}
- ${project.build.directory}
- ${project.outputDirectory}
- ${project.testOutputDirectory}
- ${project.groupId}
- ${project.versioin}
- ${project.build.finalName}
- 自定义属性:在 POM 中元素下自定义 Maven 属性
- ${hello.world}
- Settings 属性:指 settings.xml 文件中 XML 元素的值,可以通过 settings.开头的属性来引用
- Java 系统属性:java 系统属性可以使用 Maven 属性引用
- ${user.home}
- 环境变量属性:所有环境变量可以使用 env. 开头的 Maven 属性引用
- ${env.JAVA_HOME}
Maven Profile
profile 能够在构建的时候修改 POM 的子集,或者添加额外的配置元素。用户可以使用很多方式激活 profile,以实现构建在不同环境下的移植
profile 配置
POM 中的元素可以定义多个 profile
<project>
...
<profiles>
<profile>
<id>dev</id>
<properties>
<k1>v1</k1>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<k1>v2</k1>
</properties>
</profile>
</profiles>
...
</project>
激活 profile
- 命令行激活(-P)
- mvn clean package -Pdev-x,dev-y(激活 dev-x和 dev-y 两个 profile)
- settings 文件显式激活
配置 settings.xml 文件的 activeProfiles 元素,对于所有项目都处于激活状态
<settings>
<activeProfiles>
<activeProfile>dev-x</activeProfile>
<activeProfile>dev-y</activeProfile>
</activeProfiles>
</settings>
- 系统属性激活
用户可以配置当某个系统属性存在时,自动激活 profile。
<profiles>
<profiler>
<activation>
<property>
<name>test</name>
</property>
</activation>
</profiler>
</profiles>
可以进一步配置当某系统属性 test 存在,且值等于 x 的时候激活 profile
<profiles>
<profiler>
<activation>
<property>
<name>test</name>
<value>x</value>
</property>
</activation>
</profiler>
</profiles>
可以在命令行声明系统属性来激活 profile
mvn clean install -Dtest=x
- 操作系统环境激活
<profiles>
<profiler>
<activation>
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
</activation>
</profiler>
</profiles>
- 文件存在是否激活
Maven 能够根据项目中某个文件存在与否来决定是否激活 profile
<profiles>
<profiler>
<activation>
<file>
<missing>x.properties</missing>
<exists>y.properties</exists>
</file>
</activation>
</profiler>
</profiles>
- 默认激活
用户可以在定义 profile 的时候指定其默认激活
<project>
...
<profiles>
<profile>
<id>dev</id>
<properties>
<k1>v1</k1>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<k1>v2</k1>
</properties>
</profile>
</profiles>
...
</project>
profile 种类
根据具体需要,可以在以下位置声明 profile
- pom.xml:pom.xml 中声明的 profile 只对当前项目有效
- 用户 settings.xml:用户目录下 .m2/settings.xml 中的 profile 对该用户所有 Maven 项目有效
- 全局 settings.xml:Maven 安装目录下 conf/settings.xml 中的 profile 对本机上所有 Maven 项目有效
Maven 插件
Maven 的核心是一个插件执行框架,所有工作都是由插件来完成。
核心插件
maven-clean-plugin
作用:清理编译生成的目标文件和目录
绑定:clean 生命周期中的 clean 阶段,绑定的是 clean 目标,实际执行是 pre-clean 和 clean 目标
- 执行命令
$mvn clean
- 清理自动化
将 clean 绑定到 default 生命周期的 initialize 阶段,配置如下
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>...</version>
<executions>
<execution>
<id>auto-clean</id>
<phase>initialize</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 跳过删除工作目录
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>...</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
- 删除其他文件或目录
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>...</version>
<filesets>
<fileset>
<directory>${basedir}/report</directory>
</fileset>
</filesets>
</plugin>
</plugins>
</build>
maven-compiler-plugin
作用:完成项目工程编译任务
绑定:default 生命周期的 compile 阶段,按顺序执行直到 compile 阶段的所有阶段。compile 阶段本身执行 compile 目标
- 指定编译的 java 版本(方式一)
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>...</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
- 指定编译的 java 版本(方式二)
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
- 增加编译参数
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>...</version>
<configuration>
<compilerArgument>-Xlint:deprecation</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
maven-surefire-plugin
作用:运行单元测试的插件
绑定:default 生命周期的 test 阶段,按顺序执行直到 test 阶段的所有阶段。test 阶段本身执行 test 目标
- 跳过 test (方式一):会编译测试代码,但不执行
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-test-plugin</artifactId>
<version>...</version>
<configuration>
<skipTest>true</skipTest>
</configuration>
</plugin>
</plugins>
</build>
- 跳过 test(方式二):会编译测试代码,但不执行
$mvn -DskipTests test
- 跳过 test(方式三):不编译测试代码,也不执行(打包线上版本时推荐使用)
$mvn package -Dmaven.test.skip=true
maven-failsafe-plugin
作用:执行集成测试
绑定:default 生命周期的 verify 阶段,按顺序执行直到 verify 阶段的所有阶段。verify 阶段本身执行intefration-test 和 verify 目标
- 配置
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>...</version>
<executions>
<execution>
<id>integration-tests</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
maven-resources-plugin
作用:复制项目资源文件到输出目录
绑定:default 生命周期的resource 阶段
- 默认配置
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
- 增加其他资源目录
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/additional</directory>
</resource>
</resources>
</build>
- 过滤资源文件
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/additional</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>