Skip to content
  • maven是什么
  • 为什么需要maven
  • maven的安装
  • maven的基本概念
  • 父子maven
  • idea中的maven1
  • 写一个maven插件

maven是什么

maven 主要服务于 java 平台的项目信息管理、项目构建、依赖管理, maven官网

为什么我们需要maven?

想想当不使用maven时,我们是如何开发java项目的?通常我们是使用Ide(如eclipse、idea)来开发的,由idea来执行编译、打包、依赖管理;这里会存在很多的不便:

  1. 我们需要手动下载jar包,然后再通过ide来管理
  2. ide 中编译、测试、代码生成等工作都是相互独立的,难以一键完成所有的工作
  3. 更重要的是对于不同的 ide,这些设置是不同的,不利于协作。

而 maven 通过提供统一的项目结构与构建流程,提供了独立于 ide 的构建框架,可以在任何环境下对项目进行构建、打包和部署。

maven 安装

在安装 Maven前,首先确保正确安装了 JDK,Maven3.9 以后要求 JDK 1.8 及以上的版本。 以 windows 平台为例,在 Maven 官网下载页面中,选择 bin.zip 格式; image.png 将下载文件压缩到某个目录下,这里选择D:\bin\apache-maven-3.0,然后配置环境变量:

  1. 在系统变量中新建一个变量,变量名为M2_HOME,变量值为Maven的安装目录D:\bin\apache-maven-3.0。单击“确定”按钮,
  2. 接着在系统变量中找到一个名为Path的变量,在变量值的末尾上%M2_HOME%\bin;。注意:多个值之间需要有分号隔开,然后单击“确定”按钮

image.png

💡配置环境变量的作用是,当我们再命令行中输入 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 中包含的主要元素:

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
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
    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元素用于定义项目构建格式,通常设为 warjar

依赖管理

依赖管理是 maven 的核心功能之一,maven 具有强大且灵活的依赖管理机制,处理依赖的方式:直接引入依赖、传递依赖(引入依赖的依赖)、依赖继承、聚合(多模块)。

引入依赖

在 maven 中引入依赖需要在pom.xmldependencies中配置,dependencies是 pom 的基石,用于定义项目所依赖的外部库或模块。在dependencies列表下的dependency中我们指定需要引入的库的坐标、版本号,maven 会根据这些信息从中央仓库下载依赖到本地仓库,再关联到我们的项目中。

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">
  ...
  <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。

xml
<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-frameworkspring-framework所依赖的 其他 jar 包也会自动引入。

注意依赖的范围会影响传递性依赖的结果。

Maven Dependency Mediation(依赖调解)

maven 提供的传递性依赖机制,简化了依赖使用,大部分情况下,我们只需要关系项目的直接依赖,但有时候传递性依赖造成问题时候,我们就需要知道该传递性依赖是如何引入。 思考这样一个问题,项目中有这样的依赖关系: A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条传递路径上由两个版本的 X,最终会引入哪个呢?这涉及到了 Maven Dependency Mediation(依赖调解)的知识:

  1. 第一原则:路径最近者优先。该例子中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
  2. 第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜

继承

支持继承是 Maven 在管理依赖方面一个突出的能力,当我们有一些公用的一些依赖构建配置等,我们可以构建一个父 maven 项目,在父 maven 项目中引入这些公用的内容,子项目通过直接继承简化重复声明的不便。

注意,对于父项目 pom 或聚合项目 pom 文件中packaging 属性通常设为 pom,从而限定在打包阶段不会执行打包。

比如我们开发 springboot 项目,通常会继承springboot-parent,它提供了一系列常用的配置和依赖管理,包含大量常用库的版本定义、默认插件配置、默认属性和环境配置等。

xml
<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 中定义的版本,从而避免在每个子模块中重复指定版本号,从而减少版本冲突。

xml
<!-- 父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 又有特殊之处:

  1. 聚合项目的 pom 中其打包方式packaging的值必须为pom,否则就无法构建。
  2. 在聚合项目的 pom 文件中使用modules中声明子模块列表 ,
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>
 
  <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,同时用户也可以自己配置了插件的行为。生命周期和插件的关系如图所示: image.png理解上边这幅图很重要: maven 构建项目的流程流程就是按顺序执行 phase,当遇到某个 phase 中绑定插件的goal,就立即执行该 goal

生命周期

初学者往往会以为Maven的生命周期是一个整体,其实不然,Maven拥有三套相互独立的生命周期,它们分别为cleandefaultsite**clean**生命周期的目的是清理项目,**default**生命周期的目的是构建项目,而site**生命**周期的目的是建立项目站点。 每个生命周期包含一些阶段(phase),这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。 以clean生命周期为例,它包含的阶段有pre-clean、clean和post-clean。 比如我们在命令行执行$ maven clean命令,pre-clean和clean阶段会得以顺序执行,而 post-clean 不会执行。

celan 生命周期包含的 phase 如下:

PhaseDescription
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 进行绑定,以完成特定的构建任务: image.png

内置绑定插件

maven 内置了一些核心插件,并进行了默认绑定,当在命令行调用生命周期阶段时,对应的插件目标就会执行。 clean 生命周期和 site 生命周期相对简单。default 生命周期与插件目标的默认绑定关系相对复杂一些,对于不同的**对于不同的 packaging 类型,默认绑定的插件也不同。 ** 比如在 maven3.92 中,对于打包类型为jar类型,默认绑定如下:

序号阶段插件Goal
1process-resourcesorg.apache.maven.plugins:maven-resources-plugin:3.3.0resources
2compileorg.apache.maven.plugins:maven-compiler-plugin:3.10.1compile
3process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:3.3.0testResources
4test-compileorg.apache.maven.plugins:maven-compiler-plugin:3.10.1testCompile
5testorg.apache.maven.plugins:maven-surefire-plugin:3.0.0test
6packageorg.apache.maven.plugins:maven-jar-plugin:3.3.0jar
7installorg.apache.maven.plugins:maven-install-plugin:3.1.0install
8deployorg.apache.maven.plugins:maven-deploy-plugin:3.1.0deploy

而对于打包类型为war,默认绑定为:

序号Maven PhasePluginGoal
1process-resourcesorg.apache.maven.plugins:maven-resources-plugin:3.3.0resources
2compileorg.apache.maven.plugins:maven-compiler-plugin:3.10.1compile
3process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:3.3.0testResources
4test-compileorg.apache.maven.plugins:maven-compiler-plugin:3.10.1testCompile
5testorg.apache.maven.plugins:maven-surefire-plugin:3.0.0test
6packageorg.apache.maven.plugins:maven-war-plugin:3.3.2war
7installorg.apache.maven.plugins:maven-install-plugin:3.1.0install
8deployorg.apache.maven.plugins:maven-deploy-plugin:3.1.0deploy

pom.xml 中 build 配置

常用的 maven 插件总结 | 海汼部落 高品质的 大数据技术社区

❓问题 1:maven build配置中executions有哪些属性,是做什么用的

ChatGPT: 在 Maven 构建配置中,executions 元素用于定义对插件目标执行的细节,包括插件目标的调用时机、顺序、配置参数等。executions 元素可以包含以下属性:

  1. id:该属性用于唯一标识一个插件目标执行的实例。如果没有提供 id,Maven 会自动生成一个唯一的标识符。
  2. phase:指定插件目标执行的阶段。通过指定 phase 属性,可以告知 Maven 何时执行插件目标。常见的阶段包括 compile, test, package, install, deploy 等。
  3. goals:指定要执行的插件目标名称。通常,在 goals 属性中指定一个或多个目标名称,用逗号分隔。
  4. configuration:定义插件目标的配置参数。在 configuration 元素中可以设置插件目标的具体属性值,以满足项目的需求。

通过在 Maven 插件配置中使用 executions 元素,可以详细地控制插件目标的执行过程和行为,使得插件能够按照项目的特定需求和规范进行执行。

参考

《maven 实战》