一、Maven简介
1.1基本介绍
Maven官方文档:Maven官方文档。
Maven是一款为Java项目构建管理、依赖管理的工具,使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。常见Maven使用场景:
- 项目中需要第三方库(依赖),如Druid连接池、MySQL数据库驱动和Jackson等,可将依赖的信息写到maven的配置文件中,之后maven会自动下载并复制这些依赖到项目中,并确保依赖版本正确无冲突和完整性。
- maven可管理项目的编译、测试、打包、部署等构建过程,通过实现标准的构建生命周期,maven可确保每一个构建过程都遵循同样的规则和最佳实践。同样,maven的插件机制也使得开发者可对构建过程进行扩展和定制。
1.2maven的安装与配置
1.2.1安装maven
安装条件:
- 需要本机已配置Java环境。
- 本机应包含JAVA_HOME环境变量。
可在命令行中使用maven命令判断是否完成安装:
C:\Users\28591>mvn -v
Apache Maven 3.9.7 (8b094c9513efc1b9ce2d952b3b9c8eaedaf8cbf0)
Maven home: C:\Users\28591\maven\apache-maven-3.9.7
Java version: 22.0.1, vendor: Oracle Corporation, runtime: C:\Users\28591\JDK
Default locale: zh_CN, platform encoding: UTF-8
OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"
在解压文件后可得到maven有如下文件结构:
- bin:maven运行脚本。
- boot:plexus-classworlds类加载器框架。
- conf:settings.xml配置文件。
- lib:Maven运行时所需要的java类库。
- LICENSE.txt、NOTICE.txt、README.txt:针对Maven版本,第三方软件等简要介绍。
1.2.2maven配置
在完成maven安装后,需要修改maven/conf/setting.xml
配置文件,来修改maven的一些默认配置,主要修改依赖本地缓存位置、maven下载镜像、maven选用编译项目的jdk版本。
- 配置本地仓库(settings标签下):本地存储依赖的位置。
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<localRepository>C:\Users\28591\maven\apache-maven-3.9.7\Maven Repository</localRepository>
- 配置国内阿里镜像(mirrors标签下):Maven会默认指向一个国外的下载地址用于下载依赖,下载速度过慢。
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirrors>
- 选用编译项目的jdk版本(profiles标签下):maven会根据选择的jdk版本构建项目,使得有些java新特性无法支持。
<profiles>
<profile>
<!--添加jdk编译版本-->
<id>jdk-22</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>22</jdk>
</activation>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<maven.compiler.compilerVersion>22</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
3.idea配置本地maven
idea工具默认使用自带的maven配置软件,需要修改配置才能使用本地配置的maven。
1.3Maven仓库
Maven 在某个统一的位置存储所有项目的构件,这个统一的位置,我们就称之为仓库,即,仓库就是存放依赖和插件的地方。项目构建完成生成的构件,也可以安装或者部署到仓库中,供其他项目使用。Maven仓库可分为本地仓库与远程仓库,根据依赖坐标查找仓库顺序为:
- 首先查看本地仓库,若本地仓库存在此构件,则直接使用。
- 若本地仓库不存在此构件,Maven 就会去远程仓库查找,若发现所需的构件后,则下载到本地仓库使用。
- 如果本地仓库和远程仓库都没有所需的构件,则 Maven 就会报错。
本地仓库实际就是本地计算机上的一个目录,默认地址为C:%USER_HOME%.m2\repository
,而远程仓库可分为中央仓库、私服仓库、其他公共仓库三类:
- 中央仓库: Maven 社区提供的一种特殊的远程仓库,它包含了绝大多数流行的开源构件。在默认情况下,当本地仓库没有 Maven所需的构件时,会首先尝试从中央仓库下载。
- 私服:一种特殊的远程仓库,它通常设立在局域网内,用来代理所有外部的远程仓库。它的好处是可以节省带宽,比外部的远程仓库更加稳定。
- 公共仓库:除了中央仓库和私服外,还有很多其他公共仓库,例如 JBoss Maven 库,Java.net Maven 库等。
maven的配置文件settings.xml中提供了<mirror>
标签用于拦截依赖请求,并将它们指向预先配置好的镜像仓库地址,当Maven 向原始的远程仓库请求依赖时,Maven 会依次查找 settings.xml 中的镜像列表,根据<mirrorOf>
标签指定的匹配规则,匹配成功时Maven 不再直接访问原始远程仓库,而是转向镜像仓库请求相同的内容。配置方式:
<mirror>
:定义一个镜像,其中包含镜像仓库的唯一标识(id)、名称、URL 和<mirrorOf>
元素,用于指定该镜像是哪些远程仓库的镜像。- 若
<mirrorOf>
设置为*
,则表示此镜像将代理所有远程仓库的请求。 - 若设置为某个仓库ID,则仅代理与该ID匹配的远程仓库请求。
- 也可以配置一组仓库ID,用逗号分隔,来代理多个特定仓库的请求。
- 若
例1:
<mirrors>
<mirror>
<id>mirrorId1</id>
<mirrorOf>*</mirrorOf>
<url>http://mirror1.example.com</url>
</mirror>
<mirror>
<id>mirrorId2</id>
<mirrorOf>*</mirrorOf>
<url>http://mirror2.example.com</url>
</mirror>
</mirrors>
此时Maven 会首先尝试 mirrorId1 镜像仓库,如果在该仓库中找不到依赖,它会继续尝试 mirrorId2 镜像仓库。通过这种方式,可以优化 Maven 的依赖解析过程,确保它优先使用最快或最可靠的镜像源。
例2:
<mirror>
<id>A仓库的id</id>
<name>xxx</name>
<url>A仓库的url</url>
<mirrorOf>B仓库的id</mirrorOf>
</mirror>
表示原本通过仓库B下载的依赖会转为从仓库A下载。
例3:
一般来说,镜像匹配顺序等于列表顺序,但当出现通配符时,以准确匹配为准。
<mirror>
<id>huaweicloud</id>
<mirrorOf>*</mirrorOf>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
</mirror>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://central.maven.org/maven2</url>
<mirrorOf>central</mirrorOf>
</mirror>
虽然huaweicloud使用通配符,但从中央仓库依赖时仍选择准确匹配的alimaven。
二、基于IDEA的Maven项目创建
2.1Maven工程的GAVP属性
maven相对于之前的工程要多出一组GAVP
属性,是GroupId
、ArtifactId
、Version
、Packaing
等属性的缩写,其中前三个属性是必要的,并且在创建项目时指定,而P
有默认值,通过配置文件也可对这些属性进行修改。这四个属性主要为每个项目在maven仓库中做一个标识,有了具体的标识,maven就可对项目进行管理和互相引用。GAVP
属性遵循以下规则:
GroupId
格式:com.公司名.业务线.子业务线,如com.alibaba.sourcing.multilang
,最多有四级。ArtifactId
格式:产品线名-模块名。如tc-client、uic-api。Version
格式:主版本号.次版本号.修订号。如初始:1.0.0、修改bug:1.0.1、功能增强:1.1.1。- 主版本号:当做了不兼容API修改,或者增加了能改变产品方向的新功能。
- 次版本号:当做了向下兼容的功能性新增(新增类、接口等)。
- 修订号:修复bug,没有修改方法签名的功能加强,保持API兼容性。
Packaing
用于指示将项目打包为什么类型的文件,idea也可根据packaging的值来识别maven项目类型。
- jar(默认值):普通java工程,打包为
.jar
文件。 - war:Java的web工程,打包为
.war
文件。 - pom:代表不会打包,用来做继承的父工程。
2.2创建Maven JavaSE项目
项目结构:
注意:
- 在创建项目时并未指定
Version
,其被赋予默认值<version>1.0-SNAPSHOT</version>
,之后可在配置文件中修改。 名称
与组ID
、工件ID
并不冲突,前者是当前项目在当前电脑文件夹中的命名,而后者是当项目打包存储在maven仓库中的命名。- 默认情况下,项目仍使用IDEA自带的未配置maven,故需手动进行修改:
2.3创建Maven JavaEE项目
1.手动创建
- 创建一个maven javase工程。
- 手动补全web项目结构。
在上文JavaSE工程的基础上补全web项目结构(必须严格按照以下结构创建文件才能被maven识别):
web.xml常用模板:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
</web-app>
可在设置中将其设置为常用模板:
2.使用插件快速补全web项目
安装插件JBLJavaToWeb
,在项目名上右击进行转换即可:
2.4项目结构说明
2.4.1Java SE项目结构
使用maven创建Java项目后会生成固定的项目结构(默认是JavaSE项目):
路径 | 功能 |
---|---|
src/main/java | 存放Java源代码 |
src.main/resources | 存放资源文件 |
src/test/java | 存放测试源文件 |
src/test/resources | 存放测试资源文件 |
target | 存放打包输出文件 |
target/classes | 存放编译输出文件,即字节码文件 |
target/test-classes | 存放测试源代码编译生成的字节码文件 |
surefire-reports | 存放Maven 运行测试用例生成的测试报告 |
helloMaven-1.0-SNAPSHOT.jar | Maven 对项目进行打包生成的 jar 文件。 |
2.4.2Java EE项目结构
三、Maven项目构建
3.1构建与生命周期
项目的构建包含以下过程:
项目构建是指将源代码、配置文件、资源文件等转化为能够运行或部署的应用程序或库的过程。事实上,平时编写的Java源代码(.java
文件)需要被打包为可运行文件(.class
文件)的过程,就是构建,而这一过程由开发工具(IDEA、ecplise,不同开发工具构建出的软件包结构并不同)自动完成了。常见的构建工具包括Maven、Gradle、Ant等,不同的构建工具得到的软件包结构也不相同。
3.2命令方式完成项目构建
maven构建项目的过程对应着maven中的不同命令:
命令 | 功能 |
---|---|
mvn compile | 编译项目,生成target文件 |
mvn package | 打包项目,生成jar或war文件 |
mvn clean | 清理编译或打包后的项目结构 |
mvn install | 打包后上传到maven本地仓库 |
mvn deploy | 只打包,上传到maven私服仓库 |
mvn site | 生成站点 |
mvn test | 执行测试代码 |
3.2.1项目编译与清理
1.在maven中下载依赖Lombok
并编写JavaBean
2.mvn compile
- classes:java类编译出的字节码文件。
3.mvn clean
3.2.2项目测试
导入junit单元测试依赖:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
</dependency>
编写测试代码:
package org.example;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class MavenTest {
@Test
public void testAssert(){
int a=10;
int b=20;
Assertions.assertEquals(a+b,20);
}
}
使用mvn test
执行测试代码:
注意,在Maven工程中,测试类的测试类名、测试方法名必须以test
开头或结尾(大小写不区分),否则maven无法识别出测试方法、测试类(与@Test
注解无关)。
3.2.3项目报告
项目报告会保存当前项目测试的结果,这并不需要使用指令来实现,而是在项目测试后自动保存在surefire-reports
目录下与测试类名同名的txt文件中。
-------------------------------------------------------------------------------
Test set: org.example.MavenTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.041 s -- in org.example.MavenTest
3.2.4项目打包
1.jar包
命名规则:ArtifactId-版本.jar
可见,打包过程中会将源程序、测试程序编译,并执行测试代码等功能。注意,打包得到的jar包中不会包含测试程序。
2.war包
注意,可能会出现war包打包插件和jdk版本不匹配问题,需在pom.xml中添加:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
3.2.5项目安装
项目安装是指将jar包或war包安装到本地仓库中,只有将jar包或war包安装到本地仓库(一般只含有通过maven从远程仓库下载的、maven自身所用插件的jar包)中,当前项目才通过配置pom.xml文件从本地仓库中获取软件包并使用。
将上文项目使用mvn install
指令后,即可通过配置pom.xml文件的方式来获取相应的jar包并使用:
<dependency>
<groupId>org.example</groupId>
<artifactId>maven01</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
可在资源管理器中找到该jar包,路径为:本地仓库路径\org\example\maven01\1.0-SNAPSHOT
3.3构建插件、命令、生命周期命令之间的关系
上文中执行package
命令时也会自动执行compile
命令,这种行为就是因为构建生命周期产生的,构建生命周期可理解为一组固定构建命令的有序集合,触发后面的命令时,会自动触发周期前的命令。主要的两个构建生命周期:
- 清理周期:对编译生成的文件进行清理。
- 包含命令:clean
- 默认周期:包含真正构建时所需执行的所有步骤,是生命周期中最核心的部分。
- 包含命令:compile->test->package->install->deploy
构建插件、命令、生命周期命令之间的关系:周期包含若干命令,命令又与若干插件对应,构建过程实质是使用周期命令构建,最终进行构建的是插件。
四、Maven依赖管理
4.1POM文件的编写
POM(Project Object Model,项目对象模型)是 Maven 的基本组件,它是以 xml 文件的形式存放在项目的根目录下,名称为 pom.xml。POM文件将项目对象化,可在配置文件中定义了项目的基本信息,用于描述项目如何构建、声明项目依赖等。当 Maven 执行一个任务时,它会先查找当前项目的 POM 文件,读取所需的配置信息,然后执行任务。例:
<?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>
<!--公司或组织的唯一标识,且配置生成的路径也是由此生成,如org.example,maven会将项目打包的jar包存放在本地路径:/org/example-->
<groupId>org.example</groupId>
<!--项目的唯一ID,一个groupId下可能有多个项目,通过artifactId进行区分-->
<artifactId>maven01</artifactId>
<!--版本号-->
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
</dependency>
</dependencies>
</project>
groupId
、artifactId
:创建项目时指定,一般不会再修改。version
:构建过程中,项目部署时进行修改,修改规则见上文。packaging
:指定maven工程的打包方式。dependecies
:第三方依赖信息声明,是项目依赖信息的集合,其中每一个依赖都对应一个dependency
标签。dependency
:对应一个依赖项(打为jar包的maven工程),需填写其基本的GAV
属性。有以下方式获取jar包的相关信息:- maven提供了查询官网:maven官方仓库,即可查到相应的属性信息。
- maven插件
maven-search
可从国内maven仓库中获取依赖,访问速度更块。
<properties>
:声明全局变量,在其他位置可通过${变量名}
进行引用,如,对Jar包版本的统一管理:
无论 POM 文件中是否显示的声明,所有的 POM 均继承自一个父 POM,这个父 POM 被称为 Super POM。在pom的继承关系中,子pom可以覆盖父pom中的配置,按照这个规则,继承关系中的所有pom叠加到一起,就生成一个最终生效的pom。maven实际运行的过程中,执行构建操作就是按照这个最终的pom运行起来的。最终的pom也叫作有效pom翻译为effective POM,通过mvn help:effective-pom
命令就可以查看项目的最终生成的pom(有效的pom),在该pom中会给出所有隐藏的标签信息。
4.2依赖范围
可通过<dependency>
下的scope
标签来设置jar包的作用范围:编译环境、测试环境、运行环境(即最终的war包中是否可用,注意而非jar包,因为war包会包含程序运行所需的所有jar包,而jar包则由maven下载其依赖。运行环境是否可用是指依赖是否会被包装到应用程序当中)。
此处的三种环境(classpath)可在项目结构中进行查看:
4.2.1compile
编译依赖范围,是scope默认值,设置后的依赖在编译环境、测试环境、运行环境下均可用。
其中,junit-platform-commons-1.10.2.jar
、opentest4j-1.3.0.jar
、apiguardian-api-1.1.2.jar
都是pom.xml中junit-jupiter-api
所依赖的jar包。
4.2.2Test
测试依赖范围,使用此依赖范围的maven依赖,只对测试环境有效,最为常见的junit依赖只在测试环境才生效(测试程序也不会被打包到war包中)。
4.3.3Provided
已提供依赖范围,只对编译和测试环境有效,如servlet-api依赖对于编译、测试阶段是需要的,但是运行阶段,由于外部容器已经提供,故无需Maven重复引入该依赖。
tomcat服务器中已提供该jar包,故无需打包到war包中,并且需要注意服务器中所提供的servlet-api的版本。
4.4Maven工程依赖下载错误问题
常用解决方案:
- 检查网络连接和Maven仓库服务器状态。
- 确保依赖项的版本号与项目对应的版本号匹配,并检查POM文件中的依赖项是否正确,如junit4与junit5不仅要修改版本号,也要修改groupId、artifactId。
- jar包在下载过程中可能会出现丢失(下载过程中网络断连)、下载错误等情况,此时需要清除本地Maven仓库缓存(lastUpdated文件),因为只要存在lastUpdated缓存文件,刷新也不会重新下载。需要根据依赖的GAV属性查找本地仓库,最终删除内部文件即可重新下载。
关于lastUpdated文件:
.lastUpdated
文件是 Maven 在下载依赖包时的中间文件。如:
slf4j-api-1.7.5.pom.lastUpdated
surefire-junit4-2.12.4.jar.lastUpdated
在下载完成后,.lastUpdated
文件会被移除,成功下载的包文件将会出现。看到该文件可能有两种情况:
- Maven正在下载该依赖
- Maven上一次下载该依赖时发生了错误而中断:此时需要手动清理
.lastUpdated
文件(直接在本地仓库中把版本目录删除也行),否则.lastUpdated
文件既不可用,又会导致maven不会重新下载。
4.5build工程构建配置
项目构建是指将源代码、依赖库和资源文件等转换为可执行或可部署的应用程序的过程,包括编译源代码、链接依赖库、打包和部署等多个步骤。默认情况下,构建不需要额外配置,都有对应的默认值,也可在pom.xml中的<build>
标签下进行配置。
<build>
<!--设置打包名称-->
<finalName>mavenProject</finalName>
<!--设置要打包的资源位置-->
<resources>
<resource>
<!--设置资源所在目录-->
<directory>src/main/java/mapper</directory>
<includes>
<!--设置要打包的资源类型-->
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<!--引入tomcat插件(本质仍是仓库中的jar包)-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<!--设置tomcat相关配置-->
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
<server>tomcat7</server>
</configuration>
</plugin>
</plugins>
</build>
<finalName>
:默认打包名称为artifactId-版本号.jar/war
,自定义名称时无需写上后缀。<resources>
:指定构建打包时,指定包含文件格式和排除文件。比如MyBatis中有时会将用于编写SQL语句的映射文件和Mapper接口都写在src/main/java下的某个包中,此时Maven就不会将其打包到jar/war包中,需要使用标签<resources>
。<plugins>
:可在<build>
中引入插件,插件的本质仍是jar包,引入方式与普通依赖相同。在引入tomcat插件后即可直接使用tomcat,而无需进行配置。
4.6依赖传递
Java项目之间可能存在相互依赖(引用)的关系,如,项目A依赖于项目B(A->B),项目B依赖于jar包C(B->C),即A包传递依赖于jar包C,则当项目A中需要导入于B的依赖时,Maven会自动将jar包C导入,这就是依赖的传递特性。传递依赖好处在于:
- 减少重复依赖:当多个项目依赖于同一个库时,Maven可自动下载且只下载一次该库,这样可减少项目的构建时间和磁盘空间。
- 自动管理依赖:Maven可自动管理依赖项,使用依赖传递,简化了依赖项的管理,使项目构建更加可靠和一致。
- 保证依赖版本正确性:通过依赖传递而下载的依赖之间都不会存在版本兼容性问题。
例:
4.6.1依赖传递原则
传递的原则:在A依赖B、B依赖C的前提下,C能否传递依赖到A,取决于B依赖C时所使用的依赖范围以及配置:
- B依赖C时使用compile范围:可以传递。
- B依赖C时使用test或provided范围:不能传递,所以需要这样的jar包时,必须在配置文件中明确指明依赖才可以。
- B依赖C,但若配置
<optional>
标签,则依赖不可传递:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.22</version>
<optional>true</optional>
</dependency>
4.6.2依赖传递冲突
依赖传递终止包含:
- 非compile范围进行依赖传递。
- 使用optional配置终止传递。
- 依赖传递冲突(传递的依赖已存在)。
前两者上文已指出,对于第三种的情况,当直接引用或间接引用相同的jar包时,一个项目就会出现相同的重复jar包,这就是依赖传递冲突,为避免出现重复依赖,需终止依赖的传递。如:
maven有自动解决依赖冲突的呢能力,会按照自己的原则进行重复依赖选择,同时也提供了手动解决冲突的方式(不推荐)。
解决依赖冲突(如何选择重复依赖)的方式:
- maven自动选择原则
- 短路径优先原则(第一原则)
- 短路径优先原则(第一原则)
- 依赖路径长度相同的情况下,则先声明的优先(第二原则)
- 手动排除:
此时模块A需选择B传递的druid包或直接依赖的druid包,即发生依赖传递冲突。
依赖传递演示:需导入Jackson Databind
、Jackson Core
、Jackson Annotions
三个依赖,而Jackson Databind
(2.10.0版本)又依赖于Jackson Core
、Jackson Annotions
,故只需导入Jackson Databind
即可。
4.7依赖冲突
依赖之间可能存在循环依赖的情况,如:
A
−
>
B
−
>
C
−
>
A
A->B->C->A
A−>B−>C−>A
此时称为依赖冲突。当发生重复依赖导入时,Maven会自动终止依赖传递,以解决依赖冲突问题。但当发生以下情况:
A
−
>
B
(
1.0
)
C
−
>
B
(
2.0
)
A->B(1.0)\\ C->B(2.0)
A−>B(1.0)C−>B(2.0)
此时A、C依赖于B的不同版本,为避免重复导入依赖B,Maven在发生依赖冲突时遵循以下原则:
- 短则优先:依赖引用路径长度短的优先导入,如 A − > C − > B ( 1.0 ) , F − > B ( 2.0 ) A->C->B(1.0),F->B(2.0) A−>C−>B(1.0),F−>B(2.0),此时会导入依赖B(2.0)。
- 上则优先:当依赖引用路径长度相同时,在
<dependencies>
中先声明的就优先导入,如 A − > B ( 1.0 ) , C − > B ( 2.0 ) A->B(1.0),C->B(2.0) A−>B(1.0),C−>B(2.0),若先声明A,则会导入B(1.0)而非B(2.0)。
例如:
依赖关系:
A
(
1.1
)
−
>
B
(
1.1
)
−
>
C
(
1.1
)
F
(
2.2
)
−
>
B
(
2.2
)
p
o
m
声明顺序:
F
(
2.2
)
、
A
(
1.1
)
依赖关系: A(1.1)->B(1.1)->C(1.1) F(2.2)->B(2.2) pom声明顺序:F(2.2)、A(1.1)
依赖关系:A(1.1)−>B(1.1)−>C(1.1)F(2.2)−>B(2.2)pom声明顺序:F(2.2)、A(1.1)
最后导入依赖为:F(2.2)、A(1.1)、B(2.2)。因为B包存在依赖冲突时,由于F(2.2)->B(2.2)
较短,故优先导入B(2.2),并且由于发生依赖冲突,Maven不会导入后续的依赖,即C(1.1)不会被导入。
4.8手动排除依赖
依赖传递过程中,可使用<dependencies>
下的<exclusion>
标签(依赖排除标签)来排除传递来的依赖,例:
也可在模块B的druid依赖下使用<optional>true<optional>
来终止该以来的传递,但这也使得模块B无法传递依赖给别的模块,故更推荐使用<exclusions>
。
五、Maven工程继承与聚合
5.1Maven工程继承关系
Maven继承是指在Maven的项目中,让一个项目从另一个项目中继承配置的机制,继承可让我们在多个项目中共享同一配置信息,使得父工程中可同一管理项目中的依赖信息,简化项目的管理和维护工作。建立继承关系的背景:
- 对一个比较大型的项目进行了模块拆分。
- 一个project下面,创建了多个module。
- 每一个module都需要配置自己的依赖信息。
继承语法:
<!--父工程-->
<groupId>org.atguigu.maven</groupId>
<artifactId>mavenProject</artifactId>
<version>1.0-SNAPSHOT</version>
<!--当前工程作为父工程,它要管理子工程,所以打包方式为pom-->
<packaging>pom</packaging>
<!--子工程-->
<!--使用parent标签指定当前工程的父工程-->
<parent>
<!--父工程的坐标-->
<groupId>com.atguigu.maven</groupId>
<artifactId>mavenProject</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--子工程的坐标,子工程坐标中的groupId和version需与父工程一致-->
<artifactId>mavenModule</artifactId>
案例:
5.2Maven父工程统一依赖管理
子工程默认情况会直接继承父工程中的所有依赖及对应的scope范围:
事实上,在父工程中一般并不会使用<dependencies>
标签,而是使用<dependencyManagement>
(依赖管理)标签,此时子进程不会无条件继承父工程的依赖,而是手动选择需要继承的依赖。并且,在继承依赖时,只需指明groupId
与artifactId
即可:
5.3Maven工程聚合关系
Maven聚合是指将多个项目组织到一个父级项目中,以便一起构建和管理。聚合可以更好地管理一组相关的子项目,同时简化它们的构建和部署过程。聚合的作用:
- 管理多个子项目:通过聚合,可将多个子项目组合在一起,方便管理和维护。
- 构架和发布一组相关的项目:通过聚合,可在一个命令中构建和发布多个相关的项目,简化了部署和维护工作。
- 优化构建顺序:通过聚合,可对多个项目进行顺序控制,避免出现构建依赖混乱,导致构建失败的情况。
- 统一管理依赖项:通过聚合,可在父项目中管理公共依赖和插件,避免重复定义。
聚合语法:父项目中含有子工程列表
<project>
<groupId>org.example</groupId>
<artifactId>mavenParent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--当前工程作为父工程,它要管理子工程,所以打包方式为pom-->
<packaging>pom</packaging>
<modules>
<!--此处为子工程的路径,而非工程名-->
<module>mavenSon_A</module>
<module>mavenSon_B</module>
</modules>
</project>
六、Maven私服
6.1Maven私服简介
Maven私服的优势:
常见的Maven私服产品:
- Apache的Archiva
- JFrog的Artifactory
- Sonatype的Nexus(最流行、使用最广泛)
6.2Nexus下载安装
下载地址:Nexus下载地址。将下载 Nexus 安装包解压到本地磁盘,可获得 nexus-3.49.0-02 和 sonatype-work 2 个目录,
- nexus-3.49.0-02:该目录中包含了 Nexus 3.x 运行所需要的文件,如启动脚本、依赖 jar 包等。
- sonatype-work:该目录中包含了 Nexus 3.x 生成的配置文件、日志文件等。
打开nexus-3.49.0-02/bin
,其中nexus.exe是可运行的,但无法直接点击启动,需要通过命令将服务安装后再进行运行,并且nexus部分命令需要管理员的权限(win图标右键点击终端管理员
):
Windows PowerShell
版权所有(C) Microsoft Corporation。保留所有权利。
安装最新的 PowerShell,了解新功能和改进!https://aka.ms/PSWindows
加载个人及系统配置文件用了 1171 毫秒。
(base) PS C:\Users\28591> cd C:\Users\28591\nexus\nexus-3.54.1-01\bin
(base) PS C:\Users\28591\nexus\nexus-3.54.1-01\bin> ./nexus /run
使用./nexus /run
启动nexus,直到出现:
此时nexus启动成功。由于nexus本身是一个web工程,故访问方式等同于访问页面,首页地址为:http://localhost:8081
,其中8081
为默认端口号,访问页面为:
6.3Nexus初始配置
初始情况下nexus会提供七个仓库(四个maven仓库和三个nuget仓库),每个仓库都有以下属性:
- type:仓库类型。
- proxy:代理仓库用于代理远程公共仓库(实质保存了远程仓库的链接,通过代理来从远程仓库机进行下载),如 Maven 中央仓库、JBoss 远程仓库。在
Server administration and configuration
中可对远程仓库进行设置,如可将Remote storage
换为阿里仓库。 - hosted:宿主仓库,是Nexus 本地仓库,该仓库通常用来部署本地项目所产生的构件。 hosted 宿主仓库又分为:releases 和shapshots,分别表示依赖的版本的发行版、快照版。快照版依赖不能上传到发行版仓库,反之亦然。nexus做了限制(即后缀名带
releases
的jar包只能上传在releases
宿主仓库,shapshots
同理)。 - group:仓库组,用来聚合代理仓库和宿主仓库,为这些仓库提供统一的服务地址,以便 Maven 可以更加方便地获得这些仓库中的构件。
- proxy:代理仓库用于代理远程公共仓库(实质保存了远程仓库的链接,通过代理来从远程仓库机进行下载),如 Maven 中央仓库、JBoss 远程仓库。在
- Format:仓库格式,有maven2与nuget两种。
- Status:仓库的状态,只有在线状态才可被访问。
- URL:仓库的路径。
6.4Nexus基本操作
6.4.1通过Nexus下载jar包
在有了私服后,Maven的下载优先顺序为:中央仓库、私服仓库、本地仓库。可修改本地maven的配置文件setting.xml
设置私服地址。
1.设置新的本地仓库地址:本地仓库为空,确保一定会去私服下载
<localRepository>C:\Users\28591\Desktop\res</localRepository>
2.将原先的阿里云镜像删除,写上nexus镜像:
<mirror>
<id>nexus-mine</id>
<mirrorOf>central</mirrorOf>
<name>Nexus mine</name>
<!--group仓库URL-->
<url>http://localhost:8081/repository/maven-public/</url>
</mirror>
若初始配置时设置了允许匿名访问
,则此时已可正常访问仓库。但若并未设置,则需设置登录信息:
<server>
<id>nexus-mine</id>
<username>admin</username>
<password>123456789</password>
</server>
注意,<server>
标签内的id值必须与mirror中的id值相同。
3.项目构建
使用命令,如mvn clean
构建项目,由于本地仓库中不含对应jar包且私服中也不含有,故会先从中央仓库(nexus下的maven-central
,下载速度过慢时可设置为阿里仓库地址)下载到私服(nexus下的maven-public
),再由私服下载到本地仓库:
6.4.2将jar包部署到Nexus
maven工程中配置:
<!--管理工程的部署位置--->
<distributionManagement>
<snapshotRepository>
<!--id应与配置中server的id一致,因为部署时也需要进行用户登录-->
<id>nexus-mine</id>
<name>Nexus Snapshot</name>
<!--需要部署的仓库地址-->
<url>http://localhost:8081/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
使用生命周期中的deploy
部署功能(mvn deploy
):
6.4.3引用别人部署的jar包
在maven中配置标签,指定下载源仓库的地址:
<repositories>
<repository>
<id>nexus-mine</id>
<name>nexus-mine</name>
<url>http://localhost:8081/repository/maven-snapshots/</url>
<snapshots>
<!--能否使用快照版本依赖-->
<enabled>true</enabled>
</snapshots>
<releases>
<!--能否使用正式版本依赖-->
<enabled>true</enabled>
</releases>
</repository>
</repositories>
七、Maven综合案例
7.1项目需求和结构分析
项目结构:
- 父工程micro-shop:统一管理依赖与插件,打包方式为pom。
- 子工程user-service:用户服务子工程,负责处理用户相关的逻辑,如用户信息的管理、用户注册、登录等,打包方式为war。
- spring-context 6.0.6
- spring-core 6.0.6
- spring-beans 6.0.6
- common-service 6.0.6
- 子工程order-service:订单服务子工程,负责处理订单相关的逻辑,如订单的创建、订单支付、退货、订单查看等,打包方式为war。
- spring-context 6.0.6
- spring-core 6.0.6
- spring-beans 6.0.6
- common-service 6.0.6
- 子工程common-service:公共模块子工程,负责存储其他服务需要的通用工具类,其他服务依赖此模块,打包方式为jar。
- commons-io 2.11.0
- junit 5.9.2
7.2项目搭建和统一构建
父工程micro-shop的pom.xml
<?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>org.example</groupId>
<artifactId>micro-shop</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!--自动完成构建-->
<modules>
<module>common-service</module>
<module>user-service</module>
<module>order-service</module>
</modules>
<properties>
<spring.version>6.0.6</spring.version>
<jackson.version>2.15.0</jackson.version>
<commons.version>2.11.0</commons.version>
<junit.version>5.9.2</junit.version>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<!--spring-context会传递依赖core、beans-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--jackson-databind会传递依赖core、annotations-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}}</version>
</dependency>
<!--commons-io-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--统一更新子工程打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</build>
</project>
父工程中统一管理依赖与插件,由于无需编写代码,可删除src目录。
子工程common-service的pom.xml:
<?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>
<parent>
<groupId>org.example</groupId>
<artifactId>micro-shop</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-service</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--配置spring-context,继承父工程版本,自动传递core、beans-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!--配置jackson-databind,继承父工程版本,自动传递core、annotations-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!--配置commons-io,继承父工程版本-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!--配置junit,继承父工程版本-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
子工程order-service的pom.xml:
<?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>
<parent>
<groupId>org.example</groupId>
<artifactId>micro-shop</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>order-service</artifactId>
<packaging>war</packaging>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>common-service</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<!--排除依赖项commons-io-->
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<!--排除依赖项jackson-->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
子工程user-service的pom.xml:
<?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>
<parent>
<groupId>org.example</groupId>
<artifactId>micro-shop</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>user-service</artifactId>
<packaging>war</packaging>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--可使用公共模块common-service,包括其传递的依赖-->
<dependency>
<groupId>org.example</groupId>
<artifactId>common-service</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<!--排除依赖项commons-io-->
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<!--排除依赖项jackson-->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
此时通过公共子模块的传递,另外两个子模块也可使用相应的依赖: