一、认识Maven
Maven是一款基于Java平台的项目管理和整合工具,它将项目的开发和管理过程抽象成一个项目对象模型(POM)。它可以帮助我们自动完成项目的编译、测试、打包、发布以及部署等工作。
它能够帮助开发者完成以下任务:
1.构建项目
2.管理依赖
Maven的核心理念:约定优于配置。Maven对项目的目录结构、测试用例命名方式等内容做了规定,凡是使用Maven管理的项目都必须准守这些规则。
Maven构建的默认项目结构
文件 | 目录 |
---|---|
Java源代码 | src/main/java |
资源文件 | src/main/resources |
测试源代码 | src/test/java |
测试资源文件 | src/test/resources |
打包输出文件 | target |
编译输出文件 | target/classes |
二、Maven的安装与配置
Maven是一个基于Java的项目管理工具,因此最基本的要求是在计算机上安装JDK。Maven的安装教程有很多,这里不做累述,可自行百度。
三、Maven基本组件POM.xml
POM文件中定义了项目的基本信息,用于描述项目如何构建、声明项目依赖等等。
在POM中我们可以配置项目所需依赖,插件,目标等等。
POM文件中的关键元素
元素 | 描述 |
---|---|
groupId | 项目组ID,定义当前项目隶属的组织或公司,一般使公司或组织的网址或URL的反写 |
artifactId | 项目ID,通常是项目的名称。 |
version | 项目版本 |
groupId和artifactId一起定义了项目在仓库中的位置。
Super POM
所有的POM均继承自一个父POM(Super POM),它包含了一些可以被继承的默认设置。
四、Maven依赖
Maven中的任何一个依赖、插件或者项目构建的输出,供其他项目引用。我们把这些称为构件也称依赖。为了方便用户使用,每个构件都可以使用Maven坐标作为其为标识,Maven 坐标包括 groupId、artifactId、version、packaging 等元素,只要用户提供了正确的坐标元素,Maven 就能找到对应的构件。
绝大多数的依赖都可以在 https://mvnrepository.com/中获取。
五、Maven仓库
Maven仓库分为两类:本地仓库和远程仓库。
Maven查找构件时,会首先查看本地仓库,找到直接引用此构件。若本地仓库没有,则会在远程仓库中查找,找到后下载到本地仓库使用。
远程仓库可以分为3个小类:中央仓库、私服、其他公共仓库。
中央仓库
由Maven社区提供的一种特殊的远程仓库,它包含了绝大多数流行的开源构件。是默认查找的远程仓库。
私服
设立在局域网中的一种特殊的远程仓库,它用于代理所有外部的远程仓库。使用它恶意节省带宽,且比外部的远程仓库更加稳定。
六、Maven的生命周期
Maven的整个生命周期将项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建过程都进行了抽象与统一。Maven的生命周期是抽象的,它的实际工作都要通过插件中的插件目标(plugin goal)来完成。
Maven拥有的三套生命周期
clean:用于清理项目。
default:用于构建项目。
site:用于建立项目站点。
七、Maven项目的打包及如何引入本地依赖
使用mvn clean package可以将本地项目打包成一个jar文件,使用以下方式将打包的本地依赖在项目中引用。
<!--外部依赖-->
<dependency>
<groupId>net.biancheng.www</groupId>
<artifactId>helloMaven</artifactId>
<!--依赖范围-->
<scope>system</scope>
<version>1.0-SNAPSHOT</version>
<!--依赖所在位置-->
<systemPath>D:\maven\helloMaven\target\helloMaven-1.0-SNAPSHOT.jar</systemPath>
</dependency>
在以上配置中,除了依赖的坐标信息外,外部依赖还使用了 scope 和 systemPath 两个元素。
scope 表示依赖范围,这里取值必须是 system,即系统。
systemPath 表示依赖的本地构件的位置。
八、Maven站点
用户可以使用 Maven 提供的 maven-site-plugin 插件让 Maven 生成一个 Web 站点, 以站点的形式发布项目描述、SCM 地址、许可证信息,开发者信息等信息。
添加项目依赖
<build>
<plugins>
<!--添加site 插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
</plugins>
</build>
使用mvn site命令生成站定,执行完毕后,可以在项目的 target\site 目录下找到 Maven 生成的站点文件。
九、Maven Archetype
Archetype是Maven项目的模板工具包,它定义了Maven项目的基本架构。Archetype提供了数千种创建Maven项目的模板,用于快速生成项目的目录结构即POM文件。
Maven Archetype 由下面 5 个模块组成:
maven-archetype-plugin:Archetype 插件。
archetype-packaging:用于描述 Archetype 的生命周期与构建项目软件包。
archetype-models:用于描述类与引用。
archetype-common:核心类。
archetype-testing:用于测试 Maven Archetype 的内部组件。
十、Maven SNAPSHOT与RELEASE版本
SNAPSHOT版本
如果我们需要在短时间内高频率地更新代码以及发布版本,就可以使用SNAPSHOT(快照)版本,它表示当前开发进度的副本。与常规版本不同,快照版本的构件在发布时,Maven 会自动为它打上一个时间戳,有了这个时间戳后,当依赖该构件的项目进行构建时,Maven 就能从仓库中找到最新的 SNAPSHOT 版本文件。
定义一个组件或模块为快照版本,只需要在pom.xml中在本版号(version元素的值)后加上-SNAPSHOT,例:
<groupId>net.biancheng.www</groupId>
<artifactId>helloMaven</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
默认情况下对于快照本本的构件,Maven 会每天从仓库中获取一次更新,用户也可以在任何 Maven 命令中使用 -U 参数强制 Maven 检查更新。命令如下:
mvn clean package -U
RELEASE版本
RELEASE版本是不带-SHAPSHOT的版本。
区别 | SNAPSHOT版本 | RELEASE版本 |
---|---|---|
定义 | 版本中带-SNAPSHOT | 版本中不带-SNAPSHOT |
发布仓库 | SNAPSHOT快照仓库 | RELEASE发行仓库 |
是否自动从远程仓库自动获取更新 | 在不更改版本号的前提下,直接编译打包时,Maven 会自动从远程仓库上下载最新的快照版本。 | 在不更改版本号的前提下,直接编译打包时,如果本地仓库已经存在该版本的模块,则 Maven 不会主动去远程仓库下载。 |
稳定性 | 对应了大量带有时间戳的构件,具有不稳定性。 | 只对应了唯一的构件,具有稳定性。 |
使用场景 | 快照版本只应该在组织内部的项目中依赖使用。 | Maven 项目使用的组织外的依赖项都应该时发布版本的,不应该使用任何的快照版本依赖,否则会造成潜在的风险。 |
发布前是否需要修改 | 当项目经过完善的测试后,需要上线时,应该将项目从快照版本更改为发布版本 | 不需要修改 |
十、依赖传递
如果项目A依赖于项目B,项目B依赖于项目C。那么项目A就会将项目B所依赖的项目C也引到项目A中。这就是所谓的依赖传递。
通过依赖传递可能会出现依赖重复或依赖冲突的情况,针对这些情况可以使用以下功能进行处理。
1、依赖范围(Dependency scope)
编译时,Maven 会将与编译相关的依赖引入到编译 classpath 中;测试时,Maven 会将与测试相关的的依赖引入到测试 classpath 中;运行时,Maven 会将与运行相关的依赖引入到运行 classpath 中。这就是依赖范围。我们可以在POM的依赖声明中使用scope元素来指定依赖范围。
Maven中具有6种常见的依赖范围:
依赖范围 | 描述 |
---|---|
compile | scope的缺省值。使用此依赖范围,上述三种classpath均被引入。如log4j在编译、测试、运行过程中都是必须的 |
test | 只对测试classpath有效 |
- 依赖调解(Dependency mediation)
- 可选依赖(Optional dependencies)
- 排除依赖(Excluded dependencies)
- 依赖管理(Dependency management)
十一、Maven POM元素继承
当一个项目包含多个模块时,可以在该项目中再创建一个父模块,并在其 POM 中声明依赖,其他模块的 POM 可通过继承父模块的 POM 来获得对父类的相关元素包括依赖的声明。注意此时的父项目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>
<groupId>net.biancheng.www</groupId>
<artifactId>Root</artifactId>
<version>1.0</version>
<!--定义的父类 POM 打包类型使pom -->
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
子项目继承父项目的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>net.biancheng.www</groupId>
<artifactId>App-Core-lib</artifactId>
<version>1.0</version>
<parent>
<groupId>net.biancheng.www</groupId>
<artifactId>Root</artifactId>
<version>1.0</version>
<relativePath>../Root</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
子项目可以从父项目继承的POM元素有:
元素 | 描述 |
---|---|
groupId | 项目组 ID,项目坐标的核心元素 |
version | 项目版本,项目坐标的核心元素 |
description | 项目的描述信息 |
organization | 项目的组织信息 |
inceptionYear | 项目的创始年份 |
url | 项目的URL地址 |
developers | 项目的开发者信息 |
contributors | 项目的贡献者信息 |
distributionManagement | 项目的部署配置 |
issueManagement | 项目的缺陷跟踪系统信息 |
ciManagement | 项目的持续集成系统信息 |
scm | 项目的版本控制系统信息 |
mailingLists | 项目的邮件列表信息 |
properties | 自定义的Maven属性 |
dependencies | 项目的依赖配置 |
dependencyManagement | 项目的依赖管理配置 |
repositories | 项目的仓库配置 |
build | 包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等 |
reporting | 包括项目的报告输出目录配置、报告插件配置等 |
可选依赖
假设存在这样的依赖关系,A 依赖于 B,B 依赖于 X,B 又依赖于 Y。B 实现了两个特性,其中一个特性依赖于 X,另一个特性依赖于 Y,且两个特性是互斥的关系,用户无法同时使用两个特性,所以 A 需要排除 X,此时就可以在 B 中将 X 设置为可选依赖。
在 B 的 POM 关于 X 的依赖声明中使用 optional 元素,将其设置成可选依赖,示例配置如下。
<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>net.biancheng.www</groupId>
<artifactId>B</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>net.biancheng.www</groupId>
<artifactId>X</artifactId>
<version>1.0-SNAPSHOT</version>
<!--设置可选依赖 -->
<optional>true</optional>
</dependency>
</dependencies>
</project>
关于 optional 元素及可选依赖说明如下:
可选依赖用来控制当前依赖是否向下传递成为间接依赖;
optional 默认值为 false,表示可以向下传递称为间接依赖;
若 optional 元素取值为 true,则表示当前依赖不能向下传递成为间接依赖。
排除依赖
与上文的应用场景相同,也是 A 希望排除间接依赖 X,除了在 B 中设置可选依赖外,我们还可以在 A 中将间接依赖 X 排除。排除依赖是通过在 A 项目中使用 exclusions 元素实现,该元素下可以包含若干个 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.biancheng.www</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>net.biancheng.www</groupId>
<artifactId>B</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<!-- 设置排除 -->
<!-- 排除依赖必须基于直接依赖中的间接依赖设置为可以依赖为 false -->
<!-- 设置当前依赖中是否使用间接依赖 -->
<exclusion>
<!--设置具体排除-->
<groupId>net.biancheng.www</groupId>
<artifactId>X</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
关于 exclusions 元素及排除依赖说明如下:
排除依赖是控制当前项目是否使用其直接依赖传递下来的接间依赖;
exclusions 元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖;
exclusion 元素用来设置具体排除的间接依赖,该元素包含两个子元素:groupId 和 artifactId,用来确定需要排除的间接依赖的坐标信息;
exclusion 元素中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖,无需指定版本 version。
十二、dependencyManagement
Maven 可以通过 dependencyManagement 元素对依赖进行管理,它具有以下 2 大特性:
在该元素下声明的依赖不会实际引入到模块中,只有在 dependencies 元素下同样声明了该依赖,才会引入到模块中。
该元素能够约束 dependencies 下依赖的使用,即 dependencies 声明的依赖若未指定版本,则使用 dependencyManagement 中指定的版本,否则将覆盖 dependencyManagement 中的版本。
例:
<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 和 version-->
<groupId>net.biancheng.www</groupId>
<artifactId>App-Data-lib</artifactId>
<version>1.0</version>
<!--dependencyManagement 标签用于控制子模块的依赖版本等信息 -->
<!-- 该标签只用来控制版本,不能将依赖引入 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>4.9</version>
<!-- <scope>test</scope> -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>5.1.18</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>0.9.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<!--声明依赖-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
子项目只需要直接引入父项目中声明的依赖即可。
导入依赖管理
使用import关键字可以将目标 pom.xml 中的 dependencyManagement 配置导入合并到当前 pom.xml 的 dependencyManagement 中。
例:
<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 和 version-->
<groupId>net.biancheng.www</groupId>
<artifactId>App-Data-lib</artifactId>
<version>1.0</version>
<!--定义依赖管理-->
<dependencyManagement>
<dependencies>
<!--导入依赖管理配置-->
<dependency>
<groupId>net.biancheng.www</groupId>
<artifactId>Root</artifactId>
<version>1.0</version>
<!--依赖范围为 import-->
<scope>import</scope>
<!--类型一般为pom-->
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<!--声明依赖-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
十三、Maven聚合
Maven的聚合功能用于模块的整合,与父模块相似,聚合模块的打包方式(packaging)也是 pom,用户可以在其 POM 中通过 modules 下的 module 子元素来添加需要聚合的模块的目录路径。
<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>net.biancheng.www</groupId>
<artifactId>Root</artifactId>
<version>1.0</version>
<!--定义的父类pom.xml 打包类型使pom -->
<packaging>pom</packaging>
<properties>
<!-- 定义一些属性 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<log4j.version>1.2.17</log4j.version>
<junit.version>4.9</junit.version>
<system.version>1.0</system.version>
<mysql.connector.version>5.1.18</mysql.connector.version>
<c3p0.version>0.9.1</c3p0.version>
</properties>
<!--dependencyManagement 标签用于控制子模块的依赖版本等信息 -->
<!-- 该标签只用来控制版本,不能将依赖引入 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>${junit.version}</version>
<!-- <scope>test</scope> -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>${mysql.connector.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<!--引用的properties标签中定义的属性 -->
<version>${c3p0.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!--添加需要聚合的模块-->
<modules>
<module>../App-Core-lib</module>
<module>../App-Data-lib</module>
<module>../App-UI-WAR</module>
</modules>
</project>
继承与聚合的关系
Maven 的继承和聚合的目的不同,继承的目的是为了消除 POM 中的重复配置,聚合的目的是为了方便快速的构建项目。
对于继承中的父模块来说,它跟本不知道那些模块继承了它,但子模块都知道自己的父模块是谁。
对于聚合模块来说,它知道哪些模块被聚合了,但那些被聚合的模块根本不知道聚合模块的存在。
两者在结构和形式上还是有一定的共同点的,最直观的就是两者的打包方式都是 pom,两者除了 POM 外都没有实际的代码内容。
十四、Maven镜像
如果一个仓库 A 可以提供另一个仓库 B 的所有内容,那么就可以认为仓库 A 是仓库 B 的一个镜像。
使用镜像代替中央仓库
国内开发人员由于网络原因,直接从中央仓库下载构件时,速度较慢或不稳定,我们通常会使用中央仓库的国内镜像站来解决该问题。
配置 Maven 镜像的方法也非常的简单,我们只需要在 Maven 安装目录中 setting.xml 文件的 mirrors 节点中,使用 mirror 标签添加镜像的相关信息即可。
镜像是用于加速资源的下载。在apache-maven-3.6.3\conf下的settings.xml文件配置。
阿里云镜像地址
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
华为云镜像地址
<mirror>
<id>huaweicloud</id>
<name>mirror from maven huaweicloud</name>
<mirrorOf>central</mirrorOf>
<url>https://repo.huaweicloud.com/repository/maven/</url>
</mirror>
十五、Maven私服
Maven私服是一种特殊的远程仓库,它架设在局域网上,用于代理外部的远程仓库。
建立私服后,用户下载使用依赖的顺序就变成了 本地仓库——Maven私服——远程仓库。
使用Maven私服的好处
1、节省外网带宽。大量对于外部远程仓库的重复请求,会消耗很大量的带宽,利用 Maven 私服代理外部仓库后,能够消除对外部仓库的大量重复请求,降低外网带宽压力。
2、位于局域网之中,下载构建更快更稳定。
3、提高项目的稳定性,增强对项目的控制。如果不建立私服,那么 Maven 项目的构件就高度依赖外部的远程仓库,若外部网络不稳定,则项目的构建过程也会变得不稳定。
建立私服后,即使外部网络状况不佳甚至中断,只要私服中已经缓存了所需的构件,Maven 也能够正常运行。一些私服软件(如 Nexus)提供了很多额外控制功能,例如,权限管理、RELEASE/SNAPSHOT 版本控制等,可以对仓库进行一些更加高级的控制。
4、降低中央仓库得负荷压力。
十六、资源文件导出设置
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>