- maven是什么
- 为什么需要maven
- maven的安装
- maven的基本概念
- 父子maven
- idea中的maven1
- 写一个maven插件
maven是什么
maven 主要服务于 java 平台的项目信息管理、项目构建、依赖管理, maven官网
为什么我们需要maven?
想想当不使用maven时,我们是如何开发java项目的?通常我们是使用Ide(如eclipse、idea)来开发的,由idea来执行编译、打包、依赖管理;这里会存在很多的不便:
- 我们需要手动下载jar包,然后再通过ide来管理
- ide 中编译、测试、代码生成等工作都是相互独立的,难以一键完成所有的工作
- 更重要的是对于不同的 ide,这些设置是不同的,不利于协作。
而 maven 通过提供统一的项目结构与构建流程,提供了独立于 ide 的构建框架,可以在任何环境下对项目进行构建、打包和部署。
maven 安装
在安装 Maven前,首先确保正确安装了 JDK,Maven3.9 以后要求 JDK 1.8 及以上的版本。 以 windows 平台为例,在 Maven 官网下载页面中,选择 bin.zip 格式; 将下载文件压缩到某个目录下,这里选择D:\bin\apache-maven-3.0
,然后配置环境变量:
- 在系统变量中新建一个变量,变量名为
M2_HOME
,变量值为Maven的安装目录D:\bin\apache-maven-3.0。单击“确定”按钮, - 接着在系统变量中找到一个名为Path的变量,在变量值的末尾上%M2_HOME%\bin;。注意:多个值之间需要有分号隔开,然后单击“确定”按钮
💡配置环境变量的作用是,当我们再命令行中输入 maven 命令时,操作系统会按照当前目录、path 环境变量的顺序寻找寻找可执行文件。
.m2
目录
maven 依赖管理的工作机制是从本地仓库找需要的依赖,如果没有再从远程仓库中下载到本地仓库,再链接到项目中。 如果没有特殊设置,maven 本地仓库在C:\Users\Administrator.m2 目录下。
如果我们再开发过程中,想要引用自己开发的 jar 包(未发布到中央仓库)也可以放在这个本地仓库目录下。
最佳实践
一些再 maven 安装中不是必须,但十分有用的实践。
设置MAVEN_OPTS环境变量
运行mvn命令实际上是执行了Java
命令,既然是运行Java
,那么运行Java
命令可用的参数当然也应该在运行mvn命令时可用。这个时候,MAVEN_OPTS
环境变量就能派上用场。 通常需要设置MAVEN_OPTS
的值为-Xms128m-Xmx512m,因为Java默认的最大可用内存往往不能够满足Maven运行的需要,比如在项目较大时,使用Maven生成项目站点需要占用大量的内存,如果没有该配置,则很容易得到java.lang.OutOfMemeoryError。因此,一开始就配置该变量是推荐的做法
不要使用IDE内嵌的Maven
idea 通常会内嵌 maven,而这个 maven 版本会比较新,往往和本地安装的 maven 版本不一致,所以推荐再 IDE 中配置使用本地的 maven。
pom.xml
POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件,包含了 Maven 项目的所有信息,包含项目的基本信息,项目依赖、项目如何构建。
pom.xml 中有众多配置项,按作用可以分为几个类型,以下是一份 pom.xml 中包含的主要元素:
<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> <!-- pom当前模型版本,不要动他 -->
<!-- 一、基础配置 -->
<groupId>...</groupId>
<artifactId>...</artifactId> >
<version>...</version>
<packaging>...</packaging> <!-- 打包方式 -->
<dependencies>...</dependencies> <!-- 依赖列表 -->
<parent>...</parent> <!-- 继承的maven父项目 -->
<dependencyManagement>...</dependencyManagement> <!-- 依赖管理 -->
<modules>...</modules> <!-- 子模块列表 -->
<properties>...</properties> <!-- 自定义属性列表 -->
<!-- 二、项目构建 -->
<build>...</build> <!-- 构建设置 -->
<reporting>...</reporting> <!-- 报告设置 -->
<!-- 其他项目信息 -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- 环境配置-->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement> <!-- 持续集成管理 -->
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories> <!-- 配置maven仓库,通常设置为阿里云 -->
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles> <!-- 配置文件列表 -->
</project>
**必要的项目坐标:**POM 文件必须具有 project 元素和四个必需字段,用于描述项目的基本信息。
modelVersion
:模型版本,不用动它groupId
artifactId
version
<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>
<!-- 公司或者组织的唯一标志,并且配置时生成的路径也是由此生成, 如com.companyname.project-group,maven会将该项目打成的jar包放本地路径:/com/companyname/project-group -->
<groupId>com.companyname.project-group</groupId>
<!-- 项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的 -->
<artifactId>project</artifactId>
<!-- 版本号 -->
<version>1.0</version>
</project>
Packaging
元素用于定义项目构建格式,通常设为 war
或jar
。
依赖管理
依赖管理是 maven 的核心功能之一,maven 具有强大且灵活的依赖管理机制,处理依赖的方式:直接引入依赖、传递依赖(引入依赖的依赖)、依赖继承、聚合(多模块)。
引入依赖
在 maven 中引入依赖需要在pom.xml
中dependencies
中配置,dependencies
是 pom 的基石,用于定义项目所依赖的外部库或模块。在dependencies
列表下的dependency
中我们指定需要引入的库的坐标、版本号,maven 会根据这些信息从中央仓库下载依赖到本地仓库,再关联到我们的项目中。
<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">
...
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
...
</dependencies>
...
</project>
上边例子中我们引入了 Junit 库,注意 junit 库本身所依赖的其他库也会被引入。 注意到 dependency
元素下除了库的坐标外还有一些其他信息:
scope
**scope**
:依赖关系,可以理解为引入的依赖的类型,maven 对于不同的依赖关系会进行不同的处理。Maven定义了几种依赖关系,分别是:
作用域 | 描述 |
---|---|
compile | 所有阶段可见,会随项目一起打包 |
test | 仅在测试时使用,构建时不会打包 |
runtime | 在运行和测试阶段可见,但不参与编译。会在运行时加载 |
provided | 在编译和测试阶段可见,但在打包阶段不会包含在最终的部署包中。通常由运行时提供 |
system | 类似于 provided,但需要显式提供路径指向本地系统中的依赖项 |
import | 只用于在dependencyManagement中进行依赖导入,不实际引入依赖 |
注:上文中提到的编译、测试、运行、打包等涉及到 maven 项目构建过程,maven 项目构建过程包括【清理项目】→【编译项目】→【测试项目】→【生成测试报告】→【打包项目】→【部署项目】
optional
optional
:当该项目本身是依赖项时,将依赖项标记为可选
exclusions
exclusions
:有时我们引入一个依赖时并不想引入它的传递依赖,这时可以通过exclusions
元素排除它,比如有一个项目依赖于 Spring Boot Starter Web,而 Spring Boot Starter Web中包含了 Tomcat 作为默认的嵌入式容器。但是我们想要需要Jetty 作为 web 容器,这时可以使用 <exclusions>
来排除掉 Tomcat。
<project>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.6</version>
<!-- 排除Spring Boot Starter Web中的Tomcat -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加Jetty作为外部的Web容器依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>2.5.6</version>
</dependency>
</dependencies>
</project
传递依赖
传递性依赖是指:当我们在 maven 中引入一个依赖,比如spring-framework
,spring-framework
所依赖的 其他 jar 包也会自动引入。
注意依赖的范围会影响传递性依赖的结果。
Maven Dependency Mediation
(依赖调解)
maven 提供的传递性依赖机制,简化了依赖使用,大部分情况下,我们只需要关系项目的直接依赖,但有时候传递性依赖造成问题时候,我们就需要知道该传递性依赖是如何引入。 思考这样一个问题,项目中有这样的依赖关系: A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条传递路径上由两个版本的 X,最终会引入哪个呢?这涉及到了 Maven Dependency Mediation
(依赖调解)的知识:
- 第一原则:路径最近者优先。该例子中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
- 第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜
继承
支持继承是 Maven 在管理依赖方面一个突出的能力,当我们有一些公用的一些依赖、构建配置等,我们可以构建一个父 maven 项目,在父 maven 项目中引入这些公用的内容,子项目通过直接继承简化重复声明的不便。
注意,对于父项目 pom 或聚合项目 pom 文件中
packaging
属性通常设为 pom,从而限定在打包阶段不会执行打包。
比如我们开发 springboot 项目,通常会继承springboot-parent
,它提供了一系列常用的配置和依赖管理,包含大量常用库的版本定义、默认插件配置、默认属性和环境配置等。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
dependency management
dependency management
元素提供了一套用于集中管理项目依赖版本的机制,用于定义项目或子模块中的依赖版本和范围,但不实际引入这些依赖。 dependencyManagement
元素通常出现在父 POM 或聚合项目的 POM 文件中,当子模块引用一个在 dependencyManagement
中定义的依赖时,只需要引用依赖的坐标(不必指定版本),Maven 将自动使用 dependencyManagement
中定义的版本,从而避免在每个子模块中重复指定版本号,从而减少版本冲突。
<!-- 父pom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.6</version> <!-- 仅仅是声明版本号 -->
</dependency>
</dependencies>
</dependencyManagement>
<!-- 在子模块中使用时 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 不需要指定版本号 -->
</dependency>
</dependencies>
🐽与 paren 不同,parent 属性直接继承父项目的所有配置,dependency management 只用于设置版本信息,但不实际引入这些依赖
聚合
简单的项目我们通常直接采用单模块构建即可,而对于大型项目,将所有代码组织到一个模块中并不利于项目管理和多人协作。 聚合或者称为多模块,是指 maven 提供的一种管理多个模块的一张机制,Maven 聚合项目是指一个 Maven 项目下包含多个子模块,每个子模块可以是一个独立的项目单元,而聚合项目可以统一管理这些子模块,这种结构有助于组织复杂的项目,提高代码的维护性。
聚合项目本身作为一个 maven 项目,也必须有自己的 pom.xml
,而同时其pom.xml
又有特殊之处:
- 聚合项目的 pom 中其打包方式packaging的值必须为pom,否则就无法构建。
- 在聚合项目的 pom 文件中使用
modules
中声明子模块列表 ,
<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>org.codehaus.mojo</groupId>
<artifactId>my-parent</artifactId>
<version>2.0</version>
<packaging>pom</packaging>
<modules>
<module>my-project</module>
<module>another-project</module>
<module>third-project</module>
</modules>
</project>
注意,每个 module 的值都是一个当前 pom 的相对目录,通常来说我们把聚合项目 pom.xml 放在最外侧,比如:
.
|-- my-module
| -- pom.xml
|-- another-project
| --pom.xml
|-- third-project
| --pom.xml
-- pom.xml
但当我们的项目目录结构调整后,聚合项目 pom 中也要对应调整
// 目录结构
.
|-- my-module
| `-- pom.xml
`-- parent
`-- pom.xml
// 聚合pom.xml中modules
<modules>
<module>../my-module</module>
</modules>
聚合项目使用很简单,思考 maven 对于聚合项目做了那些事情?最重要的是我们可以在聚合项目目录下执行构建命令,这样所有的子模块都会进行构建,而 maven 会构建一个反应堆构建顺序(Reactor Build Order),分析各个子模块之间的依赖关系,决定出合理的模块构建顺序。
关于 ruoyi 多模块项目打包的思考
在学习 ruoyi 项目的时候,之前一直对于打包构建的方式不太理解,通过学习 maven,解答了自己的一些疑问,可能其他人也有类似误解,这里也记录下来: ❓问题 1: 比如说admin模块并没有依赖system模块,打包后为什么直接用admin下的target目录中的jar就可以部署呢?
回答:其实admin下是主模块,包含了启动类,同时admin通过引入framework依赖,间接的引入了system模块;
❓问题 2: 关于目录结构:springboot项目bean扫描默认是扫描启动类所在包下的所有子包中的bean,对于ruoyi多模块项目,实际文件目录结构并不是启动类在根目录下?
首先区分两个概念,源码中的文件路径和编译后的类路径: 包名和目录结构虽然在源码中的表现形式是文件路径(如
src/main/java/com/sky/...
),但在编译后的class
文件中,它们被组织在一个类路径层级下。因此,Spring 在运行时扫描的是类路径,并不是直接扫描文件系统。
生命周期与插件
Maven另外两个核心概念是生命周期和插件,两者协同工作,密不可分。 maven 生命周期是对项目的构建流程进行抽象和统一,这个生命周期包含了项目的清理-初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。 Maven的生命周期是抽象的,生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务(如编译源代码)都交由插件来完成。 maven 插件机制是为大多数构建步骤提供并绑定了默认插件,例如,针对编译的插件有maven-compiler-plugin,同时用户也可以自己配置了插件的行为。生命周期和插件的关系如图所示: 理解上边这幅图很重要: maven 构建项目的流程流程就是按顺序执行 phase
,当遇到某个 phase
中绑定插件的goal
,就立即执行该 goal
。
生命周期
初学者往往会以为Maven的生命周期是一个整体,其实不然,Maven拥有三套相互独立的生命周期,它们分别为clean
、default
和site
。**clean**
生命周期的目的是清理项目,**default**
生命周期的目的是构建项目,而site**生命**
周期的目的是建立项目站点。 每个生命周期包含一些阶段(phase
),这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。 以clean生命周期为例,它包含的阶段有pre-clean、clean和post-clean。 比如我们在命令行执行$ maven clean
命令,pre-clean和clean阶段会得以顺序执行,而 post-clean 不会执行。
celan 生命周期包含的 phase 如下:
Phase | Description |
---|---|
pre-clean | 执行实际项目清理之前需要完成的工作 |
clean | 删除前一次构建生成的所有文件 |
post-clean | 执行项目清理后需要完成的工作 |
default 生命周期包含的phase如下(只列出核心阶段):
阶段名称 | Maven建议插件实现该阶段时的处理 |
---|---|
validate | 校验项目的正确性 |
compile | 编译项目中src=>main和src=>test目录下的源代码 |
test-compile | 编译项目中src=>test目录下的源代码 |
test | 执行test-compile编译后的二进制文件 |
package | 生成目标包,jar war等 |
install | 将生成的包安装到本地仓库 |
deploy | 将生成的包安装到远程仓库 |
插件 goal
我们知道 maven 中实际干活的是插件,为了便于代码的复用和项目管理, maven 设计了插件 goal 的机制,一个插件 goal 代表了一个特定的任务。比如maven-dependency-plugin
有十多个目标,每个目标对应了一个功能,比如分析项目依赖的 goal 为dependency:analyze
、列出项目依赖树的 goal 为 dependency:tree。 注意生命周期默认绑定有插件 goal,比如说当我们执行maven compile
,实际上执行的是compiler:compile
从代码的角度来理解插件 goal 更简单(源码角度也类似这样):可以将插件本身是一个 java 类,而插件 goal 其实是 类中的某个方法。
插件
插件是 maven 中非常重要的部分,maven 本质上是一个执行插件的框架,所有的工作都是由插件完成的。 maven 通过将生命周期与插件 goal 进行绑定,以完成特定的构建任务:
内置绑定插件
maven 内置了一些核心插件,并进行了默认绑定,当在命令行调用生命周期阶段时,对应的插件目标就会执行。 clean 生命周期和 site 生命周期相对简单。default 生命周期与插件目标的默认绑定关系相对复杂一些,对于不同的**对于不同的 packaging 类型,默认绑定的插件也不同。 ** 比如在 maven3.92 中,对于打包类型为jar
类型,默认绑定如下:
序号 | 阶段 | 插件 | Goal |
---|---|---|---|
1 | process-resources | org.apache.maven.plugins:maven-resources-plugin:3.3.0 | resources |
2 | compile | org.apache.maven.plugins:maven-compiler-plugin:3.10.1 | compile |
3 | process-test-resources | org.apache.maven.plugins:maven-resources-plugin:3.3.0 | testResources |
4 | test-compile | org.apache.maven.plugins:maven-compiler-plugin:3.10.1 | testCompile |
5 | test | org.apache.maven.plugins:maven-surefire-plugin:3.0.0 | test |
6 | package | org.apache.maven.plugins:maven-jar-plugin:3.3.0 | jar |
7 | install | org.apache.maven.plugins:maven-install-plugin:3.1.0 | install |
8 | deploy | org.apache.maven.plugins:maven-deploy-plugin:3.1.0 | deploy |
而对于打包类型为war
,默认绑定为:
序号 | Maven Phase | Plugin | Goal |
---|---|---|---|
1 | process-resources | org.apache.maven.plugins:maven-resources-plugin:3.3.0 | resources |
2 | compile | org.apache.maven.plugins:maven-compiler-plugin:3.10.1 | compile |
3 | process-test-resources | org.apache.maven.plugins:maven-resources-plugin:3.3.0 | testResources |
4 | test-compile | org.apache.maven.plugins:maven-compiler-plugin:3.10.1 | testCompile |
5 | test | org.apache.maven.plugins:maven-surefire-plugin:3.0.0 | test |
6 | package | org.apache.maven.plugins:maven-war-plugin:3.3.2 | war |
7 | install | org.apache.maven.plugins:maven-install-plugin:3.1.0 | install |
8 | deploy | org.apache.maven.plugins:maven-deploy-plugin:3.1.0 | deploy |
pom.xml 中 build 配置
常用的 maven 插件总结 | 海汼部落 高品质的 大数据技术社区
❓问题 1:maven build配置中executions有哪些属性,是做什么用的
ChatGPT: 在 Maven 构建配置中,executions 元素用于定义对插件目标执行的细节,包括插件目标的调用时机、顺序、配置参数等。executions 元素可以包含以下属性:
- id:该属性用于唯一标识一个插件目标执行的实例。如果没有提供 id,Maven 会自动生成一个唯一的标识符。
- phase:指定插件目标执行的阶段。通过指定 phase 属性,可以告知 Maven 何时执行插件目标。常见的阶段包括 compile, test, package, install, deploy 等。
- goals:指定要执行的插件目标名称。通常,在 goals 属性中指定一个或多个目标名称,用逗号分隔。
- configuration:定义插件目标的配置参数。在 configuration 元素中可以设置插件目标的具体属性值,以满足项目的需求。
通过在 Maven 插件配置中使用 executions 元素,可以详细地控制插件目标的执行过程和行为,使得插件能够按照项目的特定需求和规范进行执行。
参考
《maven 实战》