一、Maven简介和快速入门
1.介绍
Maven:是一款为 Java 项目构建管理、依赖管理的工具(软件),使用 Maven 可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
理解:
① maven 是什么? --> 工具和软件
② maven 为什么服务? --> Java项目
③ maven 的主要功能? --> 构建管理、依赖管理
总结:Maven就是一个软件,掌握软件安装、配置、以及基本功能(项目构建、依赖管理)使用即可。
2.Maven主要作用理解
(1)场景概念
场景一:例如我们项目需要第三方库(依赖),如Druid连接池、MySQL数据库驱动和Jackson等。那么我们可以将需要的依赖项的信息编写到 Maven 工程的配置文件,Maven 软件就会自动下载并复制这些依赖项到项目中,也会自动下载依赖所需要的依赖!确保依赖版本正确无冲突和依赖完整!
场景二:项目开发完成后,想要将项目打成.war文件,并部署到服务器中运行,使用Maven软件,我们可以通过一行构建命令(mvn package)快速项目构建和打包!节省大量时间!
(2)依赖管理
Maven 可以管理项目的依赖,包括自动下载所需依赖库、自动下载依赖需要的依赖并且保证版本没有冲突、依赖版本管理等。
通过 Maven,我们可以方便地维护项目所依赖的外部库,而我们仅仅需要编写配置即可。
(3)构建管理
项目构建是指将源代码、配置文件、资源文件等转化为能够运行或部署的应用程序或库的过程!
在过去,我们将 java 源代码(.java)构建打包成 可运行的代码(.class),借助的是开发工具。
然而,对于不同的开发工具,构建的项目格式要求不同。比如:
IDEA: src / web
eclipse:src / webContent
此时,由于构建的项目格式不同,两个开发工具构建的项目是无法互相识别的。
解决办法:使用maven
maven的优点:
① maven 构建触发方便 --> 打包:mvn package 或者可视化
② 构建要求项目结构(maven:src / webapp)
无论是何种开发工具,都统一使用 maven 进行构建,开发工具只充当代码提示的作用。
此时,就可以使用任意开发工具进行开发,构建使用 maven 工具即可。
3.maven安装和配置
(1)安装
下载完成后,解压即可。
(2)配置环境变量
在环境变量中,配置以下配置:
M2_HOME:maven目录下的 bin 目录(目前用不到,后续 spring 会用到)
MAVEN_HOME:maven 的目录
在系统的 path 中配置: %MAVEN_HOME%\bin
在 cmd 中输入:mvn -version,测试是否配置成功。
(3)配置文件
我们需要需改 maven/conf/settings.xml 配置文件,来修改maven的一些默认配置。
我们主要休要修改的有三个配置:
1.maven下载镜像
2.依赖本地缓存位置(本地仓库位置)
3.maven选用编译项目的jdk版本
a.阿里云镜像
镜像:mirrors
作用:加速我们的下载(国内建议使用阿里云的镜像)
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
b.本地仓库
建立一个本地仓库
同样在 setting.xml 中对应地方添加如下代码:
<localRepository>D:\apache-maven-3.9.8\maven-repo</localRepository>
c. 配置 jdk17 版本项目构建
maven 需要对 Java 工程进行构建,默认的构建版本可能过低,不支持 Java 一些最新的新特性。
我们需要将 jdk 的编译版本改为 jdk17,这样代码中所有的新特性构建时都支持。
<!--在profiles节点(标签)下添加jdk编译版本-->
<profile>
<id>jdk-17</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>17</jdk>
</activation>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.compilerVersion>17</maven.compiler.compilerVersion>
</properties>
</profile>
(4)idea配置本地maven
我们需要将配置好的maven软件,配置到idea开发工具中即可!
注意:idea工具默认自带maven配置软件,但是因为没有修改配置,建议替换成本地配置好的maven!
打开 idea 开发工具,依次点击 file / settings / build / build tool / maven
二、创建Maven工程
1.梳理Maven工程GAVP属性
Maven工程相对之前的工程,多出一组gavp属性,gav需要我们在创建项目的时指定,p有默认值,后期通过配置文件修改。
Maven 中的 GAVP 是指 GroupId、ArtifactId、Version、Packaging 等四个属性的缩写,其中前三个是必要的,而 Packaging 属性为可选项。这四个属性主要为每个项目在maven仓库总做一个标识,类似人的《姓-名》。有了具体标识,方便maven软件对项目进行管理和互相引用!
(1)GAV
GAV遵循以下规则:
① GroupID 格式:com.{公司/BU }.业务线.[子业务线],最多 4 级
说明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一级;子业务线可选
正例:com.taobao.tddl 或 com.alibaba.sourcing.multilang
② ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到仓库中心去查证一下。
正例:tc-client / uic-api / tair-tool / bookstore
③ Version版本号格式推荐:主版本号.次版本号.修订号 1.0.0
主版本号:当做了不兼容的 API 修改,或者增加了能改变产品方向的新功能
次版本号:当做了向下兼容的功能性新增(新增类、接口等)
修订号:修复 bug,没有修改方法签名的功能加强,保持 API 兼容性
(2)Packaging
指示将项目打包为什么类型的文件,idea根据packaging值,识别maven项目类型。
Packaging定义规则:
packaging 属性为 jar(默认值),代表普通的Java工程,打包以后是.jar结尾的文件;
packaging 属性为 war,代表Java的web工程,打包以后.war结尾的文件;
packaging 属性为 pom,代表不会打包,用来做继承的父工程。
2.idea 构建JavaSE工程
先创建一个空的工程,作为父工程,后面可以将本阶段所有的 maven 项目都放在该工程下。
在该工程下,右键,选择 new module,新建一个新的模块
注意:
① 一般情况下,ArtifactID 和工程名相同即可。
此处省略了version,直接给了一个默认值1.0-SNAPSHOT ,自己后期可以在项目中随意修改。
② 每次构建完新工程后,务必去 settings 中检查 maven 是否已经设置成本地的。
3.idea 构建JavaEE工程
(1)手动创建(不推荐)
① 创建一个javase 的 maven工程
② 手动添加web项目结构文件
注意:结构和命名固定,必须一模一样,web.xml中的内容从以前项目中复制粘贴即可。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0"
metadata-complete="true">
</web-app>
③ 修改该工程下的 pom.xml 文件打包方式
<groupId>com.mihoyo</groupId>
<artifactId>maven-javaee</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 新增一行打包方式packaging -->
<packaging>war</packaging>
(2)插件方式创建
① 安装插件 JBLJavaToWeb
② 创建一个javase 的 maven工程
③ 右键、使用插件快速补全 web 项目
4.Maven工程项目结构说明
Maven 是一个强大的构建工具,它提供一种标准化的项目结构,可以帮助开发者更容易地管理项目的依赖、构建、测试和发布等任务。
以下是 Maven Web 程序的文件结构及每个文件的作用:
|-- pom.xml # Maven 项目管理文件
|-- src
|-- main # 项目主要代码
| |-- java # Java 源代码目录
| | `-- com/example/myapp # 开发者代码主目录
| | |-- controller # 存放 Controller 层代码的目录
| | |-- service # 存放 Service 层代码的目录
| | |-- dao # 存放 DAO 层代码的目录
| | `-- model # 存放数据模型的目录
| |-- resources # 资源目录,存放配置文件、静态资源等
| | |-- log4j.properties # 日志配置文件
| | |-- spring-mybatis.xml # Spring Mybatis 配置文件
| | `-- static # 存放静态资源的目录
| | |-- css # 存放 CSS 文件的目录
| | |-- js # 存放 JavaScript 文件的目录
| | `-- images # 存放图片资源的目录
| `-- webapp # 存放 WEB 相关配置和资源
| |-- WEB-INF # 存放 WEB 应用配置文件
| | |-- web.xml # Web 应用的部署描述文件
| | `-- classes # 存放编译后的 class 文件
| `-- index.html # Web 应用入口页面
`-- test # 项目测试代码
|-- java # 单元测试目录
`-- resources # 测试资源目录
其中,常用的文件有:
pom.xml:Maven 项目管理文件,用于描述项目的依赖和构建配置等信息
src/main/java:存放项目的 Java 源代码
src/main/resources:存放项目的资源文件,如配置文件、静态资源等
src/main/webapp/WEB-INF:存放 Web 应用的配置文件
src/main/webapp/index.html:Web 应用的入口页面。
src/test/java:存放项目的测试代码。
src/test/resources:存放测试相关的资源文件,如测试配置文件等。
三、Maven核心功能依赖和构建管理
1.依赖管理和配置
Maven 依赖管理是 Maven 软件中最重要的功能之一。
我们通过定义 POM 文件,Maven 能够自动解析项目的依赖关系,并通过 Maven 仓库自动下载和管理依赖,从而避免了手动下载和管理依赖的繁琐工作和可能引发的版本冲突问题。
重点: 编写pom.xml文件!
① maven项目信息属性配置和读取
<!-- 模型版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- gavp属性 -->
<!-- 不会改变 -->
<groupId>com.mihoyo</groupId>
<artifactId>maven-javaee</artifactId>
<!-- 版本号 [构建过程 - 部署 - 修改] -->
<version>1.0.0</version>
<!--打包方式
默认:jar
jar指的是普通的java项目打包方式! 项目打成jar包!
war指的是web项目打包方式!项目打成war包!
pom不会讲项目打包!这个项目作为父工程,被其他工程聚合或者继承!后面会讲解两个概念
-->
<packaging>war</packaging>
<!-- 声明版本号 -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
② 依赖管理和添加:
<!-- 第三方依赖信息声明
dependencies - 项目依赖信息的集合
dependency - 每个依赖项
[gav] - 依赖的信息,就是其他maven的工程[jar]
-->
<dependencies>
<!-- 引入具体的依赖包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.1</version>
</dependency>
</dependencies>
细节:
① 第三方依赖信息如何知道?
方式一:maven 提供的查询官网(https://mvnrepository.com/)
方式二:maven 插件(maven-search)
安装成功后,点击 tools / Maven Search 输入信息即可。
② 后期依赖会非常多,比如:我想将 jackson 的相关依赖,全都统一成同一个版本,只能一个个找再改。
解决办法:提取版本号,统一管理
在 properties 标签中,声明一个变量,然后在其他位置就可以通过 ${变量名} 进行引用
<!-- 声明版本号 -->
<properties>
<!-- 声明一个变量: jackson.version = 2.17.1 -->
<jackson.version>2.17.1</jackson.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
注意: 声明的标签建议两层以上命名!推荐:技术名.version
③ <dependency> 中的 scope 属性:表示引入依赖的作用域
取值 | 生效范围 | 使用场景举例 |
---|---|---|
compile (默认) | main目录、test目录、打包和运行 | |
test | test目录 | junit:@Test |
runtime | 打包和运行 | MySQL:Class.forName("com.mysql.cj.jdbc.Driver") |
provided | main目录、test目录 | Servlet:HttpServlet,因为运行时 tomcat 提供了 Servlet |
总结:scope 是一种锦上添花的手段,如果不熟练,建议直接使用默认值,全部生效,就一定不会错!
2.依赖传递和冲突
(1)依赖传递
定义:导入依赖,会自动导入依赖的依赖(compile dependencies)
当我们引入一个库或框架时,构建工具(如 Maven、Gradle)会自动解析和加载其所有的直接和间接依赖,确保这些依赖都可用。
作用:
① 减少重复依赖:当多个项目依赖同一个库时,Maven 可以自动下载并且只下载一次该库。这样可以减少项目的构建时间和磁盘空间。
② 自动管理依赖: Maven 可以自动管理依赖项,使用依赖传递,简化了依赖项的管理,使项目构建更加可靠和一致。
③ 确保依赖版本正确性:通过依赖传递的依赖,之间都不会存在版本兼容性问题,确实依赖的版本正确性!
例如:项目中,需要导入jackson相关的依赖,通过之前导入经验,jackson需要导入三个依赖,分别为:
但在 data-bind 2.15.2 中,依赖其他两个依赖
所以我们可以直接导入 data-bind,maven 会自动加载该依赖传递需要的依赖。
(2)依赖冲突
定义:发现已经存在的依赖(重复依赖),会终止依赖传递,以避免循环依赖和重复依赖的问题。
依赖冲突的解决原则:
① 第一原则(短路优先原则):谁短谁优先!指引用的路径长度
A—>B—>C—>D—>E—>X(version 0.0.1)
A—>F—>X(version 0.0.2)
则 A 依赖于 X(version 0.0.2)
② 第二原则(声明优先原则):谁上谁优先!指路径相同时,在<depencies></depencies>中,先声明的,会优先选择!
A—>E—>X(version 0.0.1) [先声明]
A—>F—>X(version 0.0.2)
则 A 依赖于 X(version 0.0.1)
Test:在以下条件下,最终会导入哪些依赖?
前提: A 1.1 -> B 1.1 -> C 1.1
F 2.2 -> B 2.2
pom声明: F 2.2
A 1.1
B 2.2
答案:F 2.2、A 1.1、B 2.2(路径短),不会引入C 1.1,只要发生冲突了,后续的依赖传递全部终止!
3.依赖导入失败场景和解决方案
在使用 Maven 构建项目时,可能会发生依赖项下载错误的情况,主要原因有以下几种:
① 下载依赖时出现网络故障或仓库服务器宕机等原因,导致无法连接至 Maven 仓库,从而无法下载依赖。
解决方案:检查网络连接和 Maven 仓库服务器状态。
② 依赖项的版本号或配置文件中的版本号错误,或者依赖项没有正确定义,导致 Maven 下载的依赖项与实际需要的不一致,从而引发错误。
解决方案:确保依赖项的版本号与项目对应的版本号匹配,并检查 POM 文件中的依赖项是否正确。
③ 本地 Maven 仓库或缓存被污染或损坏,导致 Maven 无法正确地使用现有的依赖项,并且也无法重新下载!(本地仓库没有该依赖,但也不访问阿里云镜像,因为本地仓库被污染)
解决方案:清除本地 Maven 仓库缓存(lastUpdated 文件),因为只要存在lastupdated缓存文件,刷新也不会重新下载。
本地仓库中,根据依赖的gav属性依次向下查找文件夹,最终删除内部的文件,刷新重新下载即可!
@echo off
rem 这里写你的仓库路径
set REPOSITORY_PATH=D:\apache-maven-3.9.8\maven-repo
rem 正在搜索...
for /f "delims=" %%i in ('dir /b /s "%REPOSITORY_PATH%\*lastUpdated*"') do (
del /s /q %%i
)
rem 搜索完毕
pause
快捷方法:新建一个 txt 文件,复制上述代码,修改本地仓库路径后保存。
将文件后缀名修改为 .bat脚本文件,点击运行脚本,即可自动清理本地错误缓存文件!
4.扩展构建管理和插件配置
项目构建:将源代码、依赖库和资源文件等转换成可执行或可部署的应用程序的过程。
主动触发场景:
① 重新编译 : 编译不充分, 部分文件没有被编译
② 打包 : 独立部署到外部服务器软件,打包部署
③ 部署本地或者私服仓库 : maven工程加入到本地或者私服仓库,才能供其他工程引用
(1)构建方式:
方式一(命令方式构建):
命令 | 阶段 | 描述 |
---|---|---|
mvn clean | 清理 | 清理编译或打包后的项目结构,删除target文件夹 |
mvn compile | 编译 | 编译项目,生成target文件 |
mvn test | 测试 | 执行测试源码 |
mvn site | 报告 | 生成一个项目依赖信息的展示页面 |
mvn package | 打包 | 打包项目,生成war / jar 文件 |
mvn install | 部署 | 打包后上传到maven本地仓库(本地部署) |
mvn deploy | 只打包,上传到maven私服仓库(私服部署) |
注意:
① 命令执行时,必须要进入到项目的根路径(与 pom.xml 是平级的)
② 部署(install / deploy)时,必须是 jar 包形式(web 工程应该部署到服务器软件中,而不是本地仓库)。
方式二(可视化方式构建):
(2)构建命令周期
定义:一组有序的构建命令容器,触发周期后的命令,会自动触发同一周期前的命令!
作用:简化触发构建命令过程。
构建命令周期包含三个周期:
此时,如果需要执行打包命令,我们不需要 mvn compile test package,直接 mvn package即可!
我们一般执行命令前,都会先 clean,所以最佳使用方案为:mvn clean package。
(3)周期,命令和插件
周期:包含若干命令
命令:包含若干插件
使用周期命令构建,可以简化构建过程,不用去调用底层的诸多插件。
最终进行构建的是插件。
如:在打包(package)时,低版本 maven 自带的 war 包打包插件版本是 2.2,与 jdk17 不匹配。
此时,我们可以在 pom.xml 中手动配置插件一个高版本的 war 包插件。
插件配置:
<build>
<!-- jdk17 和 war包版本插件不匹配 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</build>
四、Maven继承和聚合特性
1.Maven工程继承关系(依赖管理的简化)
继承:Maven 继承是指在 Maven 的项目中,让一个项目从另一个项目中继承配置信息的机制。
作用:在父工程中统一管理项目中的依赖信息,进行统一版本管理。
语法:
父工程:
<groupId>com.mihoyo</groupId>
<artifactId>maven-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
<packaging>pom</packaging>
子工程:
<!-- 使用parent标签指定当前工程的父工程 -->
<parent>
<!-- 父工程的坐标 -->
<groupId>com.mihoyo</groupId>
<artifactId>maven-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 子工程的坐标 -->
<!-- 子工程坐标中的groupId和version也会继承自父工程,同时也可以自行定义 -->
<!-- <groupId>com.mihoyo</groupId> -->
<artifactId>maven-module</artifactId>
<!-- <version>1.0-SNAPSHOT</version> -->
Test:在 shop 工程下,被划分了很多子模块,由不同开发人员进行开发。如果在不同模块中依赖了相同的依赖,但版本不同,如何解决?
思路一:直接在父工程中引入依赖,从而确定统一的版本,子模块中就不需要再引入了。
(缺点:shop-order 模块不需要该依赖,也被迫引入了,造成资源浪费。)
思路二:父工程不引入依赖,只做依赖版本的声明,让需要引入的子模块去引入。
父工程声明依赖版本:
<!-- 使用dependencyManagement标签配置对依赖的管理 -->
<!-- 被管理的依赖并没有真正被引入到工程,可以被子工程继承版本号 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
子工程引用依赖:
<!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。 -->
<!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 -->
<!-- 具体来说是由父工程的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>
注意:
① 父工程声明依赖版本后,依赖并没有真正引入。只有子工程引用了,才是真正引入了。
② 子工程引入时可以省略版本号<version>,如果版本号没有省略且与父工程不同,会就近原则优先使用子工程的版本。
2.Maven工程聚合关系(构建管理的简化)
聚合:将多个项目组织到一个父级项目中,通过触发父工程的构建,统一按顺序触发子工程构建的过程。
作用:
① 统一管理子项目构建:通过聚合,可以将多个子项目组织在一起,方便管理和维护。
② 优化构建顺序:通过聚合,可以对多个项目进行顺序控制,避免出现构建依赖混乱导致构建失败的情况。(顺序问题:A模块引用了B模块,若想构建,B必须先部署到本地仓库中,A才能引用)
语法:
父项目中包含的子项目列表:
<project>
<groupId>com.mihoyo</groupId>
<artifactId>parent-project</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<!-- 设置要统一管理哪些子工程的artifactId -->
<modules>
<module>child-project1</module>
<module>child-project2</module>
</modules>
</project>
此时,通过触发父工程构建命令,会引发所有子模块构建!
注意:
继承和聚合是两个不同的概念。默认情况下,继承后就自动会产生聚合(父工程 pom 文件中自动写好<modules>)。
但继承并不一定会聚合,因为也可以通过删除其中的<module>来取消聚合关系。