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 通过提供统一的项目结构与构建流程,提供了独立于 IDEA 的构建框架,可以在任何环境下对项目进行构建、打包和部署。

除此之外,使用 maven 还有一个额外的好处,即 Maven 对于项目目录结构、测试用例命名等内容都有既定的规则,遵循这些成熟的规则,可以免去用户在不同项目间切换的成本。

maven 安装

在安装 Maven前,首先确保正确安装了 JDK,Maven3.9 以后要求 JDK 1.8 及以上的版本。

以 windows 平台为例,在 Maven 官网下载页面中,选择 bin.zip 格式;

将下载文件压缩到某个目录下,这里选择D:\bin\apache-maven-3.0,然后配置环境变量:

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

💡配置环境变量的作用是,当我们再命令行中输入 maven 命令时,操作系统会按照当前目录、path 环境变量的顺序寻找寻找可执行文件。

.m2 目录

maven 依赖管理的工作机制是从本地仓库找需要的依赖,如果没有再从远程仓库中下载到本地仓库,再链接到项目中。

如果没有特殊设置,maven 本地仓库在C:\Users\Administrator.m2 目录下。

如果我们再开发过程中,想要引用自己开发的 jar 包(未发布到中央仓库)也可以放在这个本地仓库目录下。

设置 maven 仓库镜像

设置用户目录下 maven 地址:

xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository/>
  <interactiveMode/>
  <usePluginRegistry/>
  <offline/>
  <pluginGroups/>
  <servers/>
  <mirrors>
    <mirror>
     <id>aliyunmaven</id>
     <mirrorOf>central</mirrorOf>
     <name>阿里云公共仓库</name>
     <url>https://maven.aliyun.com/repository/central</url>
    </mirror>
    <mirror>
      <id>repo1</id>
      <mirrorOf>central</mirrorOf>
      <name>central repo</name>
      <url>http://repo1.maven.org/maven2/</url>
    </mirror>
    <mirror>
     <id>aliyunmaven</id>
     <mirrorOf>apache snapshots</mirrorOf>
     <name>阿里云阿帕奇仓库</name>
     <url>https://maven.aliyun.com/repository/apache-snapshots</url>
    </mirror>
  </mirrors>
  <proxies/>
  <activeProfiles/>
  <profiles>
    <profile>  
        <repositories>
           <repository>
                <id>aliyunmaven</id>
                <name>aliyunmaven</name>
                <url>https://maven.aliyun.com/repository/public</url>
                <layout>default</layout>
                <releases>
                        <enabled>true</enabled>
                </releases>
                <snapshots>
                        <enabled>true</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>MavenCentral</id>
                <url>http://repo1.maven.org/maven2/</url>
            </repository>
            <repository>
                <id>aliyunmavenApache</id>
                <url>https://maven.aliyun.com/repository/apache-snapshots</url>
            </repository>
        </repositories>             
     </profile>
  </profiles>
</settings>

最佳实践

在 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

项目基本信息,项目的依赖、项目如何构建

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

build 配置

build 元素用于配置项目目录结构和管理插件。

build 元素从概念中可以分为两个部分:

  • BaseBuild type

maven archetype 是 maven 项目模板工具,开发者使用预先定义好的 archetype 可以快速创建项目,并保持团队内部一致。

你也可以创建自己的 archetype,maven 也提供了一些 archetype,常见的如下:

Archetype** **ArtifactIdsDescription
maven-archetype-archetype用于构造自定义原型项目的原型
maven-archetype-j2ee-simpleJ2EE 项目原型
maven-archetype-plugin自定义插件原型
maven-archetype-quickstart一个快速 maven 项目原型
maven-archetype-webappwebapp maven 项目原型

依赖管理

依赖管理是 maven 的核心功能之一,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定义了几种依赖关系,分别是:

注:上文中提到的编译、测试、运行、打包等涉及到 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 放在最外侧,比如:

plain
.
 |-- my-module
 |   -- pom.xml
 |-- another-project
 |	 --pom.xml
 |-- third-project
 |	 --pom.xml
 -- pom.xml

但当我们的项目目录结构调整后,聚合项目 pom 中也要对应调整

plain
// 目录结构
.
 |-- 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拥有三套相互独立的生命周期,它们分别为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如下(只列出核心阶段):

插件 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 类型,如 jar 或 war, 默认绑定的插件也不同。 **

比如在 maven3.92 中,对于打包类型为jar类型,默认绑定如下:

序号
1
2
3
4
5
6
7
8

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

序号
1
2
3
4
5
6
7
8

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 实战》