一文流:Maven精讲

news/2025/1/13 0:58:06 标签: maven, java

一文流系列是作者苦于技术知识学了-忘了背了-忘了的苦恼,决心把技术知识的要点一笔笔✍️出来,一图图画出来,一句句讲出来,以求刻在🧠里。

该系列文章会把核心要点提炼出来,以求掌握精髓,至于其他细节,写在文章里,留待后续回忆。

目前进度请查看:

:::info
https://www.yuque.com/u1949579/vtk1e4/fuq6986htl8yy9bg?singleDoc# 《我的技术栈-思维导图》

:::

Maven是什么呀?

是一款功能强大的项目构建和依赖管理工具。简单来说,就是负责编译代码、运行测试、打包项目的工具。

如果你连编译测试、运行测试、打包项目都不知道的话,看下面的内容:

  1. 编译测试
    • 简单来说,编译测试就是把程序员写的代码(通常是高级编程语言,如 Java、C++ 等)转换成计算机能够理解并执行的机器语言形式的过程。在这个过程中会检查代码的语法是否正确。
  2. 运行测试
    • 运行测试是在编译成功后,让程序在特定的环境(如开发环境、测试环境)中实际执行的过程。这主要是为了检查程序的功能是否符合预期。
  3. 打包项目
    • 打包项目是将项目相关的所有文件(包括代码文件、配置文件、资源文件等)整理成一个可以方便分发和部署的形式。例如Java web项目,代码会被打包成jar文件或者war文件。

在没有Maven之前,流行的项目打包工具为 ant,ant 意即蚂蚁, 是Apache 基金会下的跨平台(基于JAVA)的构件工具。

每个ant项目都包含一个 ant脚本(默认是build.xml) , ant脚本中包含一个project标签, project 标签中包含多个 target标签, target标签中包含多个 task,task是一个标签组的概念,包含 usage, clean, javac,jar, sql,delete等众多具体标签, 每个task 标签有特定的用法,例如 delete 就是用于删除文件或路径的标签。一份ant的配置大体如下:

<?xml version="1.0" encoding="GBK"?>
[code]<!-- 定义生成文件的project根元素,默认的target为空 -->
<project name="antQs" basedir="." default="">
  <!-- 定义三个简单属性 -->
  <property name="src" value="src"/>
  <property name="classes" value="classes"/>
  <property name="dest" value="dest"/>
  <!-- 定义一组文件和目录集 -->
  <path id="classpath">
    <pathelement path="${classes}"/>
  </path>
  <!-- 定义help target,用于输出该生成文件的帮助信息 -->
  <target name="help" description="打印帮助信息">
    <echo>help - 打印帮助信息</echo>
    <echo>compile - 编译Java源文件</echo>
    <echo>run - 运行程序</echo>
    <echo>build - 打包JAR包</echo>
    <echo>clean - 清除所有编译生成的文件</echo>
  </target>
  <!-- 定义compile target,用于编译Java源文件 -->
  <target name="compile" description="编译Java源文件">
    <!-- 先删除classes属性所代表的文件夹 -->
    <delete dir="${classes}"/>
    <!-- 创建classes属性所代表的文件夹 -->
    <mkdir dir="${classes}"/>
    <!-- 编译Java文件,编译后的class文件放到classes属性所代表的文件夹内 -->
    <javac destdir="${classes}" debug="true"
      deprecation="false" optimize="false" fail>
      <!-- 指定需要编译的Java文件所在的位置 -->
      <src path="${src}"/>
      <!-- 指定编译Java文件所需要第三方类库所在的位置 -->
      <classpath refid="classpath"/>
    </javac>
  </target>
  <!-- 定义run target,用于运行Java源文件,
  运行该target之前会先运行compile target -->
  <target name="run" description="运行程序" depends="compile">
    <!-- 运行lee.HelloTest类,其中fork指定启动另一个JVM来执行java命令 -->
    <java classname="lee.HelloTest" fork="yes" fail>
      <classpath refid="classpath"/>
      <!-- 运行Java程序时传入2个参数 -->
      <arg line="测试参数1 测试参数2"/>
    </java>
  </target>
  <!-- 定义build target,用于打包JAR文件,
  运行该target之前会先运行compile target -->
  <target name="build" description="打包JAR文件" depends="compile">
    <!-- 先删除dest属性所代表的文件夹 -->
    <delete dir="${dest}"/>
    <!-- 创建dest属性所代表的文件夹 -->
    <mkdir dir="${dest}"/>
    <!-- 指定将classes属性所代表的文件夹下的所有
    *.classes文件都打包到app.jar文件中 -->
    <jar destfile="${dest}/app.jar" basedir="${classes}"
      includes="**/*.class">
      <!-- 为JAR包的清单文件添加属性 -->
      <manifest>
        <attribute name="Main-Class" value="lee.HelloTest"/>
      </manifest>
    </jar>
  </target>
  <!-- 定义clean target,用于删除所有编译生成的文件 -->
  <target name="clean" description="清除所有编译生成的文件">
    <!-- 删除两个目录,目录下的文件也一并删除 -->
    <delete dir="${classes}"/>
    <delete dir="${dest}"/>
  </target>
</project>

可以想见,即便一个简单的项目,基本都需要经过 初始化,目录结构搭建, 编译, 打包等过程,而当你使用ant时,需要手动的定义每一个过程需要完成的工作,是比较繁琐的,特别是当项目比较大的时候, 你需要手动维护庞大的依赖群, 管理依赖之间的关系。

而同样的功能,在Maven中可能的配置为:

<?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>com.example</groupId>
    <artifactId>your-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
          <plugin>
            <!-- 使用 Maven 的maven-compiler-plugin来编译 Java 源文件,
          它默认会按照 Maven 的目录结构约定(src/main/java作为源文件目录,
          target/classes作为编译输出目录)来操作 -->
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
          </plugin>
            <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-jar-plugin</artifactId>
              <version>3.3.0</version>
          </plugin>
        </plugins>
    </build>
</project>

可以看到精简了非常多,与ant专注于构建不同,maven不仅支持了ant的全部功能,还在项目文件的管理中下了一番功夫,maven的核心思想与springboot不谋而合,即:规约大于配置

  1. 首先, Maven提供了默认的文件结构 {PROJECT_HOME}/src/main/java , 因为该文件结构具有相当大的通用性, 且支持自定义。因而在搭建项目时,开发人员不再需要维护项目的目录结构
  2. 其次, Maven 内置了 更多的隐式规则, 包括构建流程,生命周期及其插件等,使得更加简单的构建项目的同时,也支持自定义项目构建流程。
  3. 添加了以 repository 和 dependency 为核心的 依赖管理,简化了依赖引用和存储。

探寻究竟, maven站在前人的基础上,意识到保持灵活的同时, 也需要做到简便, 默认的通用结构、构建规则、构建生命规则给Maven带来使用上的便利性。

**引用Maven官网的一句话:**Maven的主要目标是允许开发人员在最短的时间内理解开发工作的完整状态。

为什么要用Maven

Maven与ant或者grade等项目构建和依赖管理工具一样,都是为了解决软件工程组件构建和管理的问题。

依赖管理难题

在没有 Maven 之前,项目的依赖管理简直就是一场 “噩梦”。每当我们开发一个 Java 项目,需要引入各种第三方类库(Jar 包)时,就得手动去网上搜索并下载这些 Jar 包,然后小心翼翼地将它们导入到项目中。这过程中,一个小疏忽,比如下载错了版本,或者漏下了某个依赖包的关联包,项目就可能在编译或者运行时出现各种莫名其妙的错误。

而 Maven 的出现,彻底改变了这一困境。它内置了一个强大的依赖管理机制,只需要在项目的配置文件(pom.xml)中简单地声明项目所需要的依赖,Maven 就会自动从中央仓库(当然,也可以配置其他远程仓库)下载对应的 Jar 包及其所有传递依赖的包到本地仓库。并且,Maven 会智能地处理版本冲突问题,按照一定的规则(如最短路径优先、最先声明优先等)为项目选择最合适的依赖版本,确保各个依赖之间和谐共处,项目得以顺利编译和运行。

  <!--简洁的依赖管理配置 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
    </dependencies>

构建流程复杂

对于普通的 Java 项目而言,项目的构建与发布流程往往十分繁琐。通常,我们需要手动执行一系列命令来完成编译源代码、运行测试用例、打包项目成可执行的 Jar 包或 War 包,最后再将打包好的文件部署到服务器上。在这个过程中,每一个步骤都需要我们精确地输入命令,并且要注意各个环节的配置细节,稍有差错,就可能导致构建失败或者部署出错。

Maven 通过定义一套标准化的项目构建生命周期,将这些复杂的构建过程进行了统一封装。开发人员只需在命令行输入简单的 Maven 命令,如 “mvn clean install”,Maven 就会按照既定的生命周期顺序,依次执行清理上一次构建产生的文件、编译源代码、处理资源文件、运行测试、打包项目以及将项目安装到本地仓库等一系列操作。这一过程高度自动化,不仅减少了人为操作可能带来的错误,还极大地提高了构建的效率。尤其是在持续集成(CI)与持续部署(CD)的场景下,Maven 的这种自动化构建能力更是不可或缺,它能够无缝对接各种 CI/CD 工具,使得项目可以在代码提交后自动触发构建、测试与部署流程,确保项目始终处于可交付的状态。

团队协作困扰

在团队协作开发项目时,传统的依赖管理方式会带来诸多不便。由于每个项目都需要依赖大量的外部 Jar 包,如果将这些 Jar 包直接放在项目代码库中,一方面会使得项目代码库变得异常臃肿,占用大量的存储空间,增加代码管理的难度;另一方面,当团队成员需要更新某个 Jar 包版本或者切换分支时,极易出现 Jar 包版本不一致的问题,进而引发各种难以排查的错误。

Maven 通过引入仓库的概念,巧妙地解决了这些问题。它将 Jar 包统一存储在仓库(本地仓库和远程仓库)中,项目代码中只需通过 pom.xml 文件记录依赖的坐标信息,而无需实际存储 Jar 包。这样一来,本地仓库可以被多个项目复用,团队成员之间共享项目代码时,无需再传输大量的 Jar 包,只需专注于代码的同步。同时,当项目需要新的依赖时,Maven 会根据 pom.xml 的配置自动从仓库下载对应的 Jar 包,确保每个开发人员在自己的开发环境中都能快速获取到一致的依赖版本,极大地提升了团队协作的效率与稳定性。

Maven安装与使用

安装条件

在着手安装 Maven 之前,务必确保本机已经安装了 Java 环境,并且正确配置了 JAVA_HOME 环境变量。这是因为 Maven 自身是基于 Java 开发的,运行时依赖于 Java 虚拟机(JVM)。检查 Java 环境是否安装就绪,可以在命令行窗口输入 “java -version”,若能正常显示 Java 的版本信息,如 “java version "1.8.0_221” 之类的字样,则表明 Java 已安装成功;接着查看是否存在 JAVA_HOME 环境变量,在 Windows 系统下,右键点击 “此电脑”,选择 “属性”,再进入 “高级系统设置”,点击 “环境变量”,在系统变量列表中查找名为 “JAVA_HOME” 的变量,其值应指向 Java 的安装目录,例如 “C:\Program Files\Java\jdk1.8.0_221”。若未满足这些条件,需先安装 Java 并配置好相应环境变量,否则 Maven 将无法正常运行。

软件安装

Maven 是一款绿色免安装软件,安装过程极为简便。从官网(https://maven.apache.org/download.cgi)下载对应操作系统的 Maven 压缩包,通常是 zip 格式(Linux 系统为 tar.gz 格式)。下载完成后,找到压缩包所在位置,右键点击选择解压到指定目录即可,例如解压到 “D:\maven\apache-maven-3.8.4”。解压后的目录结构包含多个重要文件夹:

  • bin 目录下存放着用于执行 Maven 各种命令的脚本文件,如 “mvn.cmd”(Windows 系统);
  • conf 目录内有核心配置文件 settings.xml,后续我们会对其进行详细配置;
  • lib 目录则包含了 Maven 运行所需的各种类库,支撑着 Maven 的各项功能实现。

环境变量配置

配置环境变量是让系统能够识别 Maven 命令的关键步骤。首先,新建一个系统变量,名为 “MAVEN_HOME”,变量值设置为 Maven 的解压路径,例如 “D:\maven\apache-maven-3.8.4”;接着,在系统变量 “Path” 中添加 “% MAVEN_HOME%\bin”,确保在命令行的任意目录下都能调用 Maven 命令。配置完成后,在命令行输入 “mvn -v”,若能输出 Maven 的版本信息,如 “Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537; 2021-06-18T09:53:26+08:00) ”,则说明环境变量配置成功。若遇到问题,仔细检查变量名、变量值是否拼写错误,以及 Path 变量中的配置项是否正确分隔(不同路径间以英文分号 “;” 隔开)。

配置文件修改

打开 Maven 解压目录下的 “conf\settings.xml” 文件,这里面包含诸多重要配置项。首先是本地仓库位置配置,默认情况下,Maven 会将下载的 Jar 包等构件存储在用户目录下的 “.m2\repository” 文件夹,若想更改到其他位置,如 “D:\maven-repo”,找到 “” 标签,将其内容修改为自定义路径 “D:\maven-repo”。为提高依赖下载速度,通常会配置国内镜像源。以阿里云镜像为例,在 “” 标签内添加如下配置:

<mirror>
    <id>aliyun</id>
    <mirrorOf>central</mirrorOf>
    <name>Aliyun Maven Mirror</name>
    <url>https://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>

这使得 Maven 优先从阿里云镜像站点下载依赖,大大加快下载进程。

IDEA 配置本地 Maven

对于使用 IDEA 开发 Java 项目的开发者而言,让 IDEA 关联本地安装的 Maven 至关重要。打开 IDEA,依次点击 “File -> Settings -> Build, Execution, Deployment -> Build Tools -> Maven”,在右侧的 “Maven home directory” 处选中本地 Maven 的安装目录,如 “D:\maven\apache-maven-3.8.4”;“User settings file” 指向之前修改的 settings.xml 文件路径;“Local repository” 则为配置的本地仓库路径。配置完成后,点击 “OK” 保存设置。为确保配置生效,还可以点击下方的 “Maven” 工具栏中的 “Reimport All Maven Projects” 按钮,让 IDEA 重新导入项目依赖,若项目依赖下载正常,无报错信息,说明本地 Maven 在 IDEA 中配置成功,后续便可顺畅地使用 Maven 进行项目构建与管理。

Maven 配置文件 settings.xml

打开 Maven 解压目录下的 “conf\settings.xml” 文件,这里面包含诸多重要配置项。其完整结构如下(为了方便阅读,删除了注释部分):

<?xml version="1.0" encoding="UTF-8"?>
<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>/path/to/local/repo</localRepository>
  
  <interactiveMode>true</interactiveMode>
  
  <offline>false</offline>
  
  <pluginGroups>
  </pluginGroups>
  
  <proxies>
  </proxies>

  <servers>
  </servers>

  <mirrors>
  </mirrors>

  <profiles>
  </profiles>

  <activeProfiles>
  </activeProfiles>
  
</settings>

conf 目录 和 .m2 目录

如果你是第一次安装maven,你能够在安装目录下的conf目录中找到settings.xml 配置文件,但如果你第一次使用maven进行项目构建后,你会发现在你的用户目录中,会出现一个.m2的隐藏(windows中非隐藏,. 前缀为linux内核操作系统中的隐藏文件前缀)目录。通常,我们会把 **conf目录 **中的settings.xml文件复制到 .m2目录中进行使用。实际上这是基于操作系统本身,相对于maven使用用户的一次分隔,不同的用户登录操作系统后将使用不同的settings.xml配置文件。如何达到这一目的,maven通过内置的settings.xml加载规则完成。

settings.xml加载规则

如上图所示,maven在构建项目获取配置文件时,首先会查找用户目录下的.m2目录,如果存在则使用,如果不存在再获取安装目录下conf目录中的配置文件,因此我们这样描述两个目录中配置文件的不同, .m2目录是用户级的配置,而conf目录是系统全局的配置。


注: 如果需要在项目构建时使用特定的配置文件,可以使用 **<font style="color:#F5222D;">mvn -s 配置文件绝对路径 + 构建命令</font>** 完成构建

settings.xml 内容解析

</>

settings.xml 除了默认的xml头之外,固定由一个 settings 标签包裹其他内容标签。用户通常不需要修改该标签内容

<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">
<!--Xml name space 的缩写,标识了该文档使用 http://maven.apache.org/SETTINGS/1.0.0 的命名空间  -->
xmlns="http://maven.apache.org/SETTINGS/1.0.0"
<!--标识xsd文件的命名空间,使用标准的http://www.w3.org/2001/XMLSchema-instance  -->
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!--标识xsd(文档结构定义文件)的URI -->
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"
<!--如对该部分内容感兴趣,可以访问 https://blog.csdn.net/lengxiao1993/article/details/77914155  -->
</settings>

</>

输入内容:仓库目录的绝对路径

默认值: ${user.home}/.m2/repository

释义:配置maven 本地依赖仓库的绝对路径。也就是你通过pom.xml文件的dependencies定义的依赖,在maven构建(package 和 install 命令)时将被下载到这个本地仓库。

注:建议将仓库配置到系统运行盘以外的磁盘。

</>

输入内容:true | false

默认值: true

释义:Maven在运行时是否需要和用户交互以获得输入。如果Maven需要和用户交互以获得输入,则设置成true,反之则应为false。默认为true。

测试了一下,不知道这个交互体现在哪里,由于这个内容资料较少,且似乎没有太大的用处,因而这里不多讨论。

</>

输入内容:true | false

默认值: false

释义: 是否脱机运行,默认时false,也就是在构建项目时链接网络,如果设置为true,可能在下载依赖或者远程部署时会发生错误,建议保持默认值

</>

组成:</>

释义: 常用插件组的定义,包含多个 </> 标签。

maven除了提供内置的clean,test,package,install,deploy等插件之外, 还支持引入第三方插件。

在使用插件时,正式的命令是 mvn 插件groupId:插件artifactId:插件目的goal 例如 clean插件, mvn org.apache.maven.plugins:maven-clean-plugin:clean 。但如果你已经使用了一段时间maven,你会发生,更常使用的命令是 mvn clean:clean 或者 mvn clean。为什么可以使用这些简写呢?正是由于pluginGroups和插件调用规则起的作用。

pluginGroups默认配置了 <font style="color:#F5222D;">org.apache.maven.plugins</font><font style="color:#F5222D;">org.codehaus.mojo</font> ;即如下所示

<pluginGroups>
  <pluginGroup>org.apache.maven.plugins</pluginGroup>
  <pluginGroup>org.codehaus.mojo</pluginGroup>
</pluginGroups>

一旦在pluginGroups中配置了插件的GroupId之后,也就意味着该插件组是你的常用插件(个人理解),你就可以不书写groupId, 直接通过书写插件的昵称进行调用。

例如我在项目中引入mybatis-generator插件

<!--注意,仅仅引入插件并不能完成自动生成工作,仍然需要引入相关的依赖包和配置文件 -->
<!--如果想要了解mybatis包括其衍生框架的代码生成工具,
可以查阅https://blog.csdn.net/weixin_43740223/article/details/108283753 -->
...
<build>
<plugin>
  <groupId>org.mybatis.generator</groupId>
  <artifactId>mybatis-generator-maven-plugin</artifactId>
  <version>1.4.0</version>
  <configuration>
    <configurationFile>${pom.basedir}/src/main/resources/maven-plugins-mybatis-generator-config.xml</configurationFile>
    <overwrite>true</overwrite>
  </configuration>
</plugin>
</build>

在未进行配置时, 我需要键入完整的插件索引才能执行插件,mvn org.mybatis.generator:mybatis-generator-maven-plugin:generate

而我们可以通过如下配置添加该插件组,此时我可以通过 mvn mybatis-generator:generate 直接进行插件的调用。

<pluginGroups>
    <!-- pluginGroup
     | Specifies a further group identifier to use for plugin lookup.
    <pluginGroup>com.your.plugins</pluginGroup>
    -->
	<pluginGroup>org.mybatis.generator</pluginGroup>
  </pluginGroups>

注1:关于插件调用规则。有人注意到 mvn clean没有输入goal也能执行,那是因为当没有goal时,maven默认goal为 插件名 ,也就是你输入 mvn clean,实际上相当于 mvn clean:clean。例如有插件为 love 且其有goal设置为love, 我可以键入 mvn love:love,也可以直接键入 mvn love,

因此,当插件的名称与goal不同名时,请注意正确使用方法。

注2:pluginGroups 似乎仅在旧版本起作用,新版本测试时发现不需要配置也可以直接使用 mvn pluginName 进行调用。不求甚解,望众位有则告知。

</>

组成:</> N

释义:配置网络代理服务器,以用于部分或全部HTTP请求。通常用不到,国内访问远程仓库可以配置仓库镜像,如果需要通过代理访问网络的话,可以用proxies配置代理。

</>

属性释义
id代理的ID,默认default
active是否激活该代理,默认true
protocol代理服务器的协议,默认http
host代理服务器的主机
port代理服务器的端口,默认8080
username代理服务器登录用户,仅在需要登录时配置
password代理服务器登录密码,仅在需要登录时配置
nonProxyHosts不使用该代理服务器的主机域名,多个域名使用

官方提供的配置示例

<proxies>
   <proxy>
      <id>example-proxy</id>
      <active>true</active>
      <protocol>http</protocol>
      <host>proxy.example.com</host>
      <port>8080</port>
      <username>proxyuser</username>
      <password>somepassword</password>
      <nonProxyHosts>www.google.com|*.example.com</nonProxyHosts>
    </proxy>
  </proxies>

</>

组成:</> N

释义:用于配置 依赖下载存储库 (POM中的repositories | pluginRepositories中定义的存储库 && settings.xml 中 profile | mirror 中定义的存储库) 以及 发布工件的存储库(POM中的distributionManagement定义的发布仓库)的鉴权信息和权限设置。

因为有些设置不适合和项目工件一同进行发布,例如服务器链接和密码,这将可能导致密码泄露。 因而可以在配置发布和依赖下载的仓库后,如果访问仓库需要进行鉴权,可以在settings.xml 中的servers块中进行定义,两者通过id进行关联。

</>
属性释义
id存储库的id,与POM中的repositories
username访问存储库的用户名,当访问需要验证身份时,配置password填写验证信息
password访问存储库的密码,当访问需要验证身份时,配置username填写验证信息
privateKey访问存储库的ss密钥路径,如果访问需要遵守ssh安全协议且采用密钥认证的话。 默认在本地的 ${user.home}/.ssh/id_dsa 目录中
passphrase访问存储库的ssh口令,如果访问需要遵守ssh安全协议且采用口令认证的话。
filePermissions发布时创建的文件的访问权限,采用*nix文件权限格式,也就是我们常用的linux文件权限一样。例如775等
directoryPermissions发布时创建的目录的访问权限,采用*nix文件权限格式,也就是我们常用的linux文件权限一样。例如775等
configuration访问存储库的一些其他配置,只有在访问特定类型的存储库时才可能用到,本文不多讨论。详情参考官网
http://maven.apache.org/guides/mini/guide-wagon-providers.html

上文中我们提到四种仓库 依赖仓库插件仓库, 镜像仓库以及 发布仓库。实际上,这几种仓库本质上都是一种仓库, 无论插件,第三方jar包还是项目构建的包文件都以几种特定的类型(jar | pom )存储在maven仓库中。

maven的仓库仅根据仓库的访问方式不同区分为 本地仓库远程仓库

本地仓库通过本机系统的文件服务器访问,远程仓库通过网络获取资源同步到本地仓库实现访问。其中远程仓库又分为 maven中央仓库, 私服(镜像)仓库, 其他仓库,之所以存在这些类型,是因为中央仓库的服务器资源有限,因而产生私服(镜像)仓库,私服(镜像)仓库将同步中央仓库的maven工件,用户在使用私服下载maven工件时通常更加高效。其他的仓库可能存储了一些特定的maven工件, 例如企业内部搭建的maven发布仓库,该企业自主研发的maven工件将存储在这些仓库中。

之所以提到这些,是希望能了解 maven仓库的一些实质后,帮助我们更好的理解 server 标签的使用。我们现在就能够这样认为, 只要你在maven项目中使用POM 或者 settings 配置了存储库(不管是发布工件的仓库,还是镜像仓库,或者一般的存储库,或者是插件仓库),如果这些存储库的访问需要验证,就可以使用 settings.xml 中的 server标签进行认证信息的设置,通过id进行关联。

注:一些推论(验证教麻烦,后续补充)。

这里还会有一个ID重复的问题, 如果你在POM 和 settings中配置了同样ID的存储库,那么根据配置文件的优先级${PROJECT_HOME}/pom.xml > ${USER_HOME}/.m2/settings.xml > ${MAVEN_HOME}/conf/settings.xml 应该是POM文件中的配置将生效

</>

这一章部分内容与上一章 </> 相关,建议一起阅读

组成:</> N

释义:配置仓库的镜像地址,相当于仓库的拦截器。当用户需要下载依赖时,maven会首先搜索本地仓库,本地仓库找不到时则查找远程仓库,如果此时发现远程仓库有镜像,则转而从镜像仓库中下载相应的工件。

注:mirror的匹配规则:使用mirrorOf配置匹配仓库ID, 且MAVEN仅使用匹配到的第一个镜像,其余符合匹配条件的镜像将不起作用。也就是说,如果你的第一个仓库的mirrorOf 配置为 * ,则其余镜像配置将不起作用

</>
属性释义
id该仓库镜像的唯一标识,与其他 mirror块 不重名即可,如果访问该镜像需要验证信息,可以在server中进行配置,通过id关联。 注:注意是配置的id,不是仓库本身的id
name该仓库镜像的名称,与其他mirror块 不重名即可
url仓库镜像的访问地址,仅支持 http 协议 和 https 协议
mirrorOf被镜像的仓库的id,允许使用通配符。如果访问的仓库的ID匹配该标签配置的内容,将采用该镜像代为执行其工作。mirrorOf支持基于字符串的统配匹配规则,具体使用可以查看官网 http://maven.apache.org/guides/mini/guide-mirror-settings.html 。 通常使用id或者 * 号进行匹配即可。

仅靠上述的描述,似乎还不能够解决一些问题,如果有多个仓库配置, maven怎么确定现在要从哪个仓库中获取依赖呢? 镜像是通过镜像ID匹配的,哪仓库的ID在哪里配置? 一个新的maven项目没有配置过任何的仓库,为什么能够下载依赖?抱着这些问题,我们继续往下看。

maven多仓库查找依赖的顺序大致如下:

  1. 在本地仓库中寻找,如果没有则进入下一步。
  2. 在全局配置的私服仓库(settings.xml中配置的并有激活)中寻找,如果没有则进入下一步。
  3. 在项目自身配置的私服仓库(pom.xml)中寻找,如果没有则进入下一步。
  4. 在默认设置的中央仓库中(id:central, url:https://repo1.maven.org/maven2/)寻找,如果没有则终止寻找。

注:

1、如果在找寻的过程中,如果发现该仓库有镜像设置,则用镜像的地址代替,例如现在进行到要在respository A仓库中查找某个依赖,但A仓库配置了mirror,则会转到从A的mirror中查找该依赖,不会再从A中查找。

2、settings.xml中配置的profile(激活的)下的respository优先级高于项目中pom文件配置的respository。

3、如果仓库的id设置成“central”,则该仓库会覆盖maven默认的中央仓库配置。

</>

组成:</> N {</>;</>;</>;</>}

释义:settings.xml 中的 profiles 是 pom.xml 中的 profiles的精简版本,相对于pom中的profiles是项目中的配置文件,它是全局的配置文件。profiles能够完成 仓库配置 和 全局属性定义。每个配置存在于一个 标签中,注意该 profile 需要进行激活才能启用

profile的三种激活方式
  1. 通过activeProfile指定profile 的 id激活
<profiles>
 	<profile>
    <id>test</id>
    <!-- 具体的一些配置-->
  </profile>
 </profiles>

  <activeProfiles>
    <!--指定profile的id进行激活 -->
    <activeProfile>test</activeProfile>
  </activeProfiles>
  1. 通过activation匹配条件激活
<profiles>
 	<profile>
    <id>test</id>
<!--
如下是官网提供的示例,我加以解释。官网文档在有些地方的释义仍然不够清晰,因此我通过搜索和测试增加了说明。
-->
    <activation>
      <!--是否默认激活-->
      <activeByDefault>false</activeByDefault>
      	<!--JDK版本,符合1.5及其子版本,例如1.5.0_06 也符合条件-->
        <jdk>1.5</jdk>
      	<!--操作系统条件 -->
        <os>
          <!--操作系统名称 通过java函数 system.getProperty("os.name")获取 -->
          <name>Windows XP</name>
          <!--操作系统类别 Unix | Windows | Mac 不区分大小写 -->
          <family>Windows</family>
          <!--计算机CPU架构 通过java函数 system.getProperty("os.arch")获取 -->
          <arch>x86</arch>
          <!--操作系统版本号 通过java函数 system.getProperty("os.version")获取 -->
          <version>5.1.2600</version>
        </os>
      	<!--Maven中定义的具体的某个属性值是否匹配, 官网中解释到:a value which can be dereferenced within the POM by ${name}
				也就是可以在POM中通过占位符${}引用到的值,那么JAVA系统变量的值(所有通过System.getProperty("propertyName")也可以都可以在此处被设置并判断,
				如果你在项目中有某些特殊的值需要借由MAVEN判断并以此激活某些profile,可以通过System.setProperties() 设置到环境变量中,也可以直接在POM中的<properties>标签中定义
				当然,在POM中直接定义有些情况下意味着你必须根据变化修改你的代码并重新编译。-->
        <property>
          <name>mavenVersion</name>
          <value>2.0.3</value>
        </property>
      	<!--根据文件是否存在判断激活 -->
        <file>
          <!--指定路径的文件是否存在 -->
          <exists>${basedir}/file2.properties</exists>
          <!--指定路径的文件是否不存在 -->
          <missing>${basedir}/file1.properties</missing>
        </file>
    </activation>
    <!--配置仓库或者全局属性等 -->
    <repositories>
      ...
    </repositories>
    <properties>
      ...
    </properties>
  </profile>
 </profiles>

如上,当profile中activation的条件全部满足时,该profile将被激活,仓库和属性的配置将生效。当然,如果你配置 <activeByDefault>true</activeByDefault> 后面的条件就不会被匹配,该配置将作为默认激活项激活。

  1. 通过命令行指令激活

mvn -P test 通过 -P 并指定 profile id 即可在运行构建时激活指定 id 的profile

Activation

略。

参考

9.</>

profile的三种激活方式

2.通过activation匹配条件激活

Properties

同pom.xml中properties标签的使用方式一致,相较于pom.xml是项目全局的属性配置 ,${user.home}/.m2/settings.xml中的properties是用户全局的属性配置。当然如果你是在 ${MAVEN_HOME}/conf/settings.xml 中配置的话就是系统全局的。

示例如下

<profiles>
    <profile>
      <id>test</id>
      ...
      <properties>
        <user.install>${user.home}/our-project</user.install>
      </properties>
      ...
    </profile>
  </profiles>

如上示例,在maven中可以通过 ${user.install} 引用该属性定义。maven的全部属性定义都可以使用占位符进行引用

Repositories && PluginRepositories

仓库配置,上文中我们已经解释了maven多仓库时的搜索机制,这里就是我们定义全局仓库的地方。仓库和插件仓库的配置是一样的, 这里使用表格简单介绍一下必要的属性配置

属性释义
id仓库id,注意与其他仓库区分
name仓库名称,方便用户的识别
url仓库访问路径
releasessnapshot
layout仓库的布局类型 default

首先,简单说明下maven的发布版本和快照版本

maven的工件分为release 和 snapshot 两种版本类型,release为稳定的发行版本,snapshot为不稳定版本,通常我们开发中都使用snapshot作为版本号。定义一个组件/模块为快照版本,只需要在pom文件中在该模块的版本号后加上**-SNAPSHOT**即可(注意这里必须是大写)。release版本不允许修改,每次进行release版本修改,发布必须提升版本号。而snapshot一般是开发过程中的迭代版本,snapshot更新后,引用的项目可以不修改版本号自动下载构建。同样的,maven中的仓库也依此分为两种,snapshot快照仓库和release发布仓库。snapshot快照仓库用于保存开发过程中的不稳定版本,release正式仓库则是用来保存稳定的发行版本。

我们知道,maven的依赖管理是基于版本管理的,对于发布状态的artifact,如果版本号相同,即使我们内部的镜像服务器上的组件比本地新,maven也不会主动下载的。如果我们在开发阶段都是基于正式发布版本来做依赖管理,那么遇到这个问题,就需要升级组件的版本号,可这样就明显不 符合要求和实际情况了。但是,如果是基于快照版本,那么问题就自热而然的解决了,而maven已经为我们准备好了这一切。

maven2会根据模块的版本号(pom文件中的version)中是否带有-SNAPSHOT来判断是快照版本还是正式版本。如果是快照版本,那么在 mvn deploy时会自动发布到快照版本库中,而使用快照版本的模块,在不更改版本号的情况下,直接编译打包时,maven会自动从镜像服务器上下载最新的快照版本。如果是正式发布版本,那么在mvn deploy时会自动发布到正式版本库中,而使用正式版本的模块,在不更改版本号的情况下,编译打包时如果本地已经存在该版本的模块则不会主动去镜像服务 器上下载。

所以,我们在开发阶段,可以将公用库的版本设置为快照版本,而被依赖组件则引用快照版本进行开发,在公用库的快照版本更新后,我们也不需要修改pom文件提示版本号来下载新的版本,直接mvn执行相关编译、打包命令即可重新下载最新的快照库了,从而也方便了我们进行开发,也不冲突MAVEN的版本管理原则。

然后针对 和 中的几个属性做下详细说明

属性:enabled

键入: true | false

释义:是否启用发布 | 快照版本。

属性:updatePolicy

键入:always | daily | interval:X | never

释义:maven在构建时根据更新策略会比较本地仓库工件和远程仓库工件,如果发现远程仓库的工件有更新,将重新获取该工件。

键入值释义
always总是比较更新
daily 默认的更新策略,每天进行一次比较更新,取值为当地时间
interval:XX为分钟的整数值,构建时发现已经间隔X分钟后进行比较更新
never从不进行比较更新

注:官网说明该更新仅支持快照版本,发行版本仍然是按照版本号获取的,一旦获取后将不进行比较更新。但是releases中仍然有该部分配置,因而是否是文档错误笔者未进行验证

属性:checksumPolicy

键入:ignore | fail | warn

释义:比较工件校验和的策略。

首先需要理解校验和, 校验和是对传输位数的累加,用于校验maven构件的完整性和准确性。在传输时,发送方和接收方通过校验和检测本次数据传输是否传输完整,接受顺序是否有误(如果数据过大,传输可能是建立在多次链接之上的)。可以查阅 https://my.oschina.net/donhui/blog/325868 了解更多关于maven校验和的知识。

我们重新回到校验和的策略 checksumPolicy ,通过表格比较一下几种策略的不同

键入值释义
ignore忽略校验和
failmaven发现校验和不一致时,将导致构建失败,并抛出异常
warnmaven发现校验和不一致时,将输出警告信息,但不会导致构建失败

</>

组成:</> N

释义:用于激活profile,上文中已有描述。

<activeProfiles>
    <activeProfile>test</ activeProfile>
 </activeProfiles>

Maven的核心概念

项目对象模型 POM

POM 是 Project Object Mode 的缩写, 意即 项目对象模型。 在Maven中, 每个JAVA项目都拥有一个 pom.xml 的配置文件, pom 文件如同项目的身份卡,里面记录了该项目的信息。

  1. 唯一的索引值, Maven 项目通过POM文件中的 groupIdartifactIdversion 组成项目的唯一索引值, 最终组成 groupId:artifactId:version 的键值
    1. groupId:代表项目所属的组织或公司域名倒序,通常用于区分不同来源的项目
    2. artifactId:项目的名称,在同一组织下用于区分不同的项目模块
    3. version:项目的版本,“SNAPSHOT” 常用于开发阶段,表示不稳定的快照版本,正式发布时会替换为稳定版本号
  2. 父子关系,maven支持项目的继承,通过指定<parent>标签中的内容,可以向上拥有父项目的一些信息和依赖关系。所有POM文件都默认继承了MAVEN官方定义的一个父级POM文件
  3. 依赖关系,maven提供了非常简便的依赖引用方式, 通过 <dependencies> 标签可以维护多个依赖性, 你只需要声明依赖的索引即可,也就是上述的groupIdartifactIdversion
  4. 构建细节,通常情况下,默认的构建规则就能满足需要,如果需要自定义构建细节,通过<build> 标签可以进行自定义的配置。

如下展示一个简单项目的pom文件示例。

<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” >
  <!--当前唯一受支持的 POM 版本 -->
  <modelVersion> 4.0.0 </ modelVersion>
 
  <!--基础知识-->
  <groupId>com.test</ groupId>
  <artifactId>demo</ artifactId>
  <version>1.0.0-SNAPSHOT</ version>
  <packaging>jar</ packaging>
  <!--添加你需要的依赖项 -->
  <dependencies> 
    <dependency>
      	<groupId>com.test</ groupId>
  			<artifactId>demo-core</ artifactId>
  			<version>1.0.0-SNAPSHOT</ version>
    </dependency>
 </ dependencies>
  
  <!--构建设置-->
  <build> ... </ build>

</ project>

约定的目录结构

Maven 项目遵循一套约定俗成的目录结构,这就好比建筑施工时遵循的蓝图规范,使得各个项目具有统一的布局,方便开发人员快速上手与维护。一般来说,一个典型的 Maven 项目目录结构如下:

my-project
  |-- src
  |   |-- main 
  |   |   |-- java # 存放项目主程序源代码的地方,按照 Java 的包名结构组织代码
  |   |   |   |-- com
  |   |   |   |   |-- example   
  |   |   |   |   |   |-- MyApp.java
  |   |   |-- resources 用于存放项目的配置文件、资源文件等,像application.properties
                        这类配置文件就放在这里,这些资源文件会在项目构建过程中被一并处理。
  |   |   |   |-- application.properties
  |   |-- test 
  |   |   |-- java # 专门用于存放测试代码
  |   |   |   |-- com
  |   |   |   |   |-- example
  |   |   |   |   |   |-- MyAppTest.java
  |   |   |-- resources # 用于存放测试相关的资源文件
  |-- pom.xml
  |-- target #  Maven 执行构建任务时输出的结果目录,例如编译后的 class 文件、
                 打包后的 Jar 包或 War 包等都会存放在这里

坐标(GAV)

如何通过尽量少的属性来维护大量的对象呢? 不仅要做好分类工作,还要知道对象的新旧情况。maven运用了一些基础的数学知识来解决问题,通过建立一个三维坐标系来管理大量的项目对象。在上文中,我们提到了POM文件的groupId, artifactId,version属性,如果使用 groupId 作为横轴, artifactId作为竖轴, version作为纵轴, 那么我们可以在脑海中轻易构建如下图所示的这样一个结构:

这就是在三维空间中定位的 三位笛卡尔坐标系模型。如下套用官网的内容说明下三个属性:

  • groupId:这在组织或项目中通常是唯一的。例如,所有核心Maven工件都可以(当然,应该)位于groupId下org.apache.maven。组ID不一定使用点表示法,例如junit项目。注意,点号groupId不必与项目包含的包结构相对应。但是,遵循此做法是一种很好的做法。当存储在存储库中时,该组的行为与Java打包结构在操作系统中的行为非常相似。点被操作系统特定的目录分隔符(例如Unix中的“ /”)替换,该分隔符成为基础存储库中的相对目录结构。在给出的示例中,该org.codehaus.mojo组位于目录中$M2_REPO/org/codehaus/mojo
  • artifactId:artifactId通常是已知项目的名称。尽管groupId很重要,但是小组中的人们很少在讨论中提及groupId(他们通常都是相同的ID,例如MojoHaus项目的groupId :)org.codehaus.mojo。它与groupId一起创建了一个密钥,用于将该项目与世界上其他所有项目分开(至少,它应该:))。与groupId一起,artifactId完全定义了存储库中工件的居住区。对于上述项目,my-project居住在$M2_REPO/org/codehaus/mojo/my-project
  • version:这是命名难题的最后一部分。groupId:artifactId表示一个项目,但无法描述我们正在谈论的那个项目的具体化身。我们要junit:junit2018年版(4.12版)还是2007年版(3.8.2版)?简而言之:代码更改,应对这些更改进行版本控制,并且此元素使这些版本保持一致。它还可以在工件的存储库中使用,以将版本彼此分开。my-project版本1.0文件位于目录结构中$M2_REPO/org/codehaus/mojo/my-project/1.0

仓库

仓库是 Maven 存放 Jar 包以及其他项目构建输出构件的地方,它分为本地仓库和远程仓库。本地仓库是 Maven 在本地机器上存储依赖的 “根据地”,默认位置在用户目录下的.m2/repository文件夹中。例如,在 Windows 系统下,一般是C:\Users\用户名\.m2\repository。当 Maven 构建项目时,首先会在本地仓库中查找所需的依赖,如果本地仓库没有找到对应的 Jar 包,它才会转向远程仓库去下载。开发人员也可以根据自己的需求,通过修改 Maven 的配置文件(settings.xml)来自定义本地仓库的位置,比如将其设置到空间更大、管理更方便的磁盘分区,像D:\maven-repo,只需要在 settings.xml 文件中找到<localRepository>标签,将其路径修改为自定义路径即可。

远程仓库则是存储在网络上的资源库,其中最知名的就是 Maven 中央仓库,它汇聚了海量的开源 Java 库和项目,几乎涵盖了开发中所需的绝大多数依赖。其默认地址为https://repo.maven.apache.org/maven2/,当本地仓库缺失依赖时,Maven 会自动尝试从中央仓库下载。然而,由于中央仓库位于国外,在国内访问时网络速度可能较慢,为了解决这个问题,国内的一些大厂或组织搭建了中央仓库的镜像站点,如阿里云镜像仓库(https://maven.aliyun.com/nexus/content/groups/public/)。

Maven 基本命令

查询版本(-v)

在命令行输入 “mvn -v”,这是我们检查 Maven 是否安装成功以及查看其版本信息的常用操作。当正确安装并配置好 Maven 后,执行该命令,控制台会输出类似如下信息:

Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537; 2021-06-18T09:53:26+08:00)
Maven home: D:\maven\apache-maven-3.8.4
Java version: 1.8.0_221, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk1.8.0_221\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "Windows 10", version: "10.0", arch: "amd64", family: "windows"

这里,我们能清晰看到 Maven 的版本 “3.8.4”,其安装目录 “D:\maven\apache-maven-3.8.4”,以及所依赖的 Java 版本 “1.8.0_221” 等关键信息。倘若执行命令后,出现诸如 “不是内部或外部命令” 之类的报错,那就需要回头检查 Maven 的安装步骤,尤其是环境变量的配置是否有误,确保 MAVEN_HOME 以及 Path 中相关配置的准确性。

编译(compile)

“mvn compile” 命令是将我们项目中的 Java 源文件编译成 class 字节码文件,编译后的文件会输出到 “target/classes” 目录下。当我们在项目开发过程中,对源代码进行了修改,想要快速检查语法错误或者将代码编译成可执行的字节码形式以供后续操作(如测试、运行)使用时,就可以执行此命令。在执行过程中,Maven 会依据 pom.xml 文件中的配置,先去本地仓库查找项目所依赖的各种类库,如果本地仓库缺失某些依赖,它会自动尝试从远程仓库下载,确保编译环境的完整性。例如,项目依赖了某个第三方的日志处理库,Maven 会自动下载该库及其传递依赖,然后运用这些依赖来成功编译项目中的源文件,整个过程无需我们手动干预,极大地简化了编译流程。

测试(test)

“mvn test” 命令用于执行项目下 “src/test/java” 目录中的单元测试类。在 Java 开发中,编写单元测试对于保证代码质量至关重要,它能够帮助我们在开发阶段尽早发现代码中的逻辑错误、边界情况处理不当等问题。Maven 执行测试命令时,只会运行那些符合特定命名规范(如类名以 “Test” 结尾,像 “XxxTest.java”)的测试类,并且会自动加载测试所需的依赖环境,确保测试的准确性与独立性。测试完成后,在 “target/surefire-reports” 目录下还会生成详细的测试报告,以 HTML 等格式呈现,方便开发人员查看测试结果,快速定位失败的测试用例,进而针对性地修复代码问题。

打包(package)

当项目开发到一定阶段,需要对外发布或者在其他环境部署时,“mvn package” 命令就派上用场了,它能够将项目打成 jar 包(对于 Java 项目而言,若是 Web 项目则会打成 war 包),并将打包后的文件放置在项目根目录下的 “target” 目录中。在执行打包操作前,要特别注意确保项目的配置正确无误,尤其是 pom.xml 文件中的依赖信息、项目版本号等。此外,如果项目对 JDK 版本有特定要求,例如项目使用了 Java 8 的某些特性,需要在 pom.xml 或 Maven 的 settings.xml 文件中明确指定 JDK 版本,否则可能出现打包失败或者在运行打包后的文件时出现兼容性问题。以在 pom.xml 中配置 JDK 1.8 为例,如下所示:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.8.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>UTF-8</encoding>
      </configuration>
    </plugin>
  </plugins>
</build>

这就保证了项目在打包过程中使用正确的 JDK 版本进行编译,避免潜在风险。

清理(clean)

“mvn clean” 命令的作用是删除项目的 “target” 文件夹,这个文件夹是 Maven 执行构建任务时存放输出结果的地方,如编译后的 class 文件、打包后的 jar 包等。随着项目的多次构建,“target” 目录下会积累大量旧的文件,这些文件不仅占用磁盘空间,有时还可能因为版本不一致等问题导致后续构建出现莫名错误。执行清理命令后,“target” 目录及其内部的所有文件都会被移除,为下一次全新的构建创造一个干净的环境,确保每次构建都基于最新的代码和配置,避免旧文件的干扰,让项目构建过程更加稳定、可靠。

安装(install)

“mvn install” 命令是将当前项目打包并安装到本地仓库中,使得该项目可以作为依赖被其他本地项目使用。这个过程其实包含了前面提到的编译、打包等步骤,首先 Maven 会对项目源代码进行编译,生成 class 文件,接着按照项目配置将其打包成 jar 包,最后将这个 jar 包安装到本地仓库的对应位置。本地仓库的默认位置在用户目录下的 “.m2/repository” 文件夹,项目安装后,其他项目在 pom.xml 文件中通过坐标引入该项目依赖时,Maven 就能直接从本地仓库找到对应的构件,实现项目间的复用,大大提高了开发效率,避免了重复开发相同功能模块的麻烦。

Maven 的高级特性

传递依赖与排除依赖

Maven 的传递依赖机制十分强大,当我们在项目的 pom.xml 文件中引入一个依赖时,Maven 不仅会下载该依赖本身,还会自动下载这个依赖所需要的其他间接依赖,也就是传递依赖。例如,当我们的项目引入了 Spring Boot 的核心启动器依赖spring-boot-starter-web,它会自动帮我们引入诸如 Spring MVC、Tomcat 等一系列相关依赖,因为spring-boot-starter-web自身在运行时也需要这些组件,Maven 的这种机制极大地简化了我们的配置过程,无需我们一个个手动去添加众多相关联的依赖。

然而,有时候这种传递依赖也可能引入一些我们不需要的组件,甚至引发版本冲突问题。这时候就需要用到排除依赖功能。假设在项目中引入了某个库 A,而库 A 又依赖了库 B,但我们项目实际上并不需要库 B,就可以在 pom.xml 文件中对库 A 的依赖配置进行如下修改:

<dependency>
    <groupId>groupA</groupId>
    <artifactId>artifactIdA</artifactId>
    <version>1.0</version>
    <exclusions>
        <exclusion>
            <groupId>groupB</groupId>
            <artifactId>artifactIdB</artifactId>
        </exclusion>
    </exclusions>
</dependency>

通过这样的配置,在引入库 A 时,就会排除掉其传递依赖的库 B,确保项目依赖的精简与准确,避免不必要的资源占用与潜在冲突。

依赖范围(scope)

Maven 中的依赖范围用于精确控制依赖与项目不同构建阶段(编译、测试、运行)的 classpath 之间的关系,不同的依赖范围有着不同的适用场景。

  • compile:这是默认的依赖范围,使用此范围的依赖在编译、测试以及运行项目时都有效。像 Spring 框架的核心模块spring-core,项目在编译源代码、运行单元测试以及最终部署上线运行时,都需要它的支持,所以通常配置为compile范围。
  • provided:适用于那些在编译和测试阶段需要,但在项目实际运行时由外部容器(如 Web 应用中的 Tomcat 服务器)提供的依赖。以servlet-api为例,项目在编译和测试 Web 相关代码时,需要这个接口来确保代码的正确性,但在将项目部署到 Tomcat 服务器时,Tomcat 自身已经包含了servlet-api,此时若 Maven 再引入一份,反而可能导致类冲突等问题,所以将其依赖范围设为provided。
  • runtime:对于一些只在项目运行时才需要的依赖,比如 JDBC 驱动程序,项目在编译主代码阶段仅仅需要依赖 JDBC 的接口,并不需要具体的驱动实现,而在执行测试用例或者实际运行项目连接数据库时,才需要对应的 JDBC 驱动,这时就适合将 JDBC 驱动的依赖范围设置为runtime。
  • test:专门用于测试阶段的依赖,如 JUnit 测试框架,它仅在执行单元测试时发挥作用,不会被包含进最终的项目构建输出(如 Jar 包或 War 包)中,确保项目发布包的精简,避免将不必要的测试类库带到生产环境。

合理配置依赖范围,能够优化项目的构建过程,减少不必要的依赖引入,提升项目的稳定性与性能。

依赖冲突解决

在复杂的项目依赖关系网中,依赖冲突是经常会遇到的问题。当一个项目通过不同的传递依赖路径引入了同一个库的不同版本时,就会产生冲突,例如项目直接依赖库 A 的 1.0 版本,同时又通过依赖库 B 间接引入了库 A 的 2.0 版本,此时 Maven 需要确定项目最终使用哪个版本的库 A。

Maven 主要采用两种策略来解决依赖冲突:短路优先和声明优先。短路优先是指在依赖路径长度不同的情况下,优先选择路径更短的依赖。例如,项目 P 依赖于模块 M1,M1 依赖于库 L 的 1.0 版本;同时项目 P 还依赖于模块 M2,M2 依赖于模块 M3,M3 依赖于库 L 的 2.0 版本,这里从项目 P 到库 L 的 1.0 版本的路径长度为 2,到 2.0 版本的路径长度为 3,所以 Maven 会优先选择库 L 的 1.0 版本。

而当依赖路径长度相同时,声明优先原则生效,即按照 pom.xml 文件中依赖声明的顺序,先声明的依赖版本优先被采用。假设项目 P 在 pom.xml 中先声明了对模块 M4 的依赖,M4 依赖库 L 的 3.0 版本,之后又声明对模块 M5 的依赖,M5 依赖库 L 的 4.0 版本,此时 Maven 会选择库 L 的 3.0 版本。开发人员可以通过查看 Maven 的依赖树(使用命令mvn dependency:tree)来排查依赖冲突,了解项目的依赖全貌,必要时手动调整依赖声明顺序或者使用依赖排除等手段来确保项目使用期望的依赖版本,保障项目稳定运行。

聚合(aggregation)

在实际的大型项目开发中,往往会拆分成多个模块进行并行开发,这些模块虽然各自独立,但又相互关联,共同构成完整的项目功能。Maven 的聚合特性就为这种场景提供了便利,通过在一个父 pom.xml 文件中进行配置,就能够将多个子模块项目统一管理起来,实现一键构建所有模块。

例如,创建一个父项目,其打包方式设置为pom,在 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>parent-project</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <modules>
    <module>module1</module>
    <module>module2</module>
    <module>module3</module>
  </modules>
</project>

上述配置中,module1、module2、module3是位于父项目目录下的子模块项目文件夹名称,它们各自有独立的 pom.xml 和源代码等。通过在父项目执行 Maven 命令,如mvn clean install,Maven 会按照顺序依次进入每个子模块目录,执行相同的构建操作,大大简化了对多个模块项目的管理流程,提高开发效率,确保各个模块的构建结果一致性。

继承(inheritance)

Maven 的继承特性类似于 Java 语言中的类继承,通过创建一个父 pom.xml 来管理通用的配置信息,子项目 pom.xml 文件继承父项目的配置,避免在每个子项目中重复配置相同的内容。

以管理依赖为例,父 pom 可以声明一些公共的依赖,如在父 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>parent-project</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.3.10</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

在子项目的 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example</groupId>
    <artifactId>parent-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../parent-project/pom.xml</relativePath>
  </parent>
  <artifactId>child-project</artifactId>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
    </dependency>
  </dependencies>
</project>

这里子项目无需再指定spring-core和slf4j-api的版本号,会自动继承父项目中定义的版本,既减少了配置的冗余,又保证了整个项目依赖版本的一致性,方便后续的维护与升级。同时,除了依赖管理,像插件配置、项目描述等诸多 POM 元素都可以通过继承机制在父子项目间共享,提升项目架构的规范性与可维护性。


http://www.niftyadmin.cn/n/5821364.html

相关文章

unity学习15:预制体prefab

目录 1 创建多个gameobject 2 创建prefab 2.1 创建prefab &#xff08;类&#xff09; 2.2 prefab 是一个文件 2.3 prefab可以导出 3 创建prefab variant &#xff08;子类&#xff09; 3.1 除了创建多个独立的prefab&#xff0c; 还可以创建 prefab variant 3.2 他…

利用logstash同步100万MySQL数据到es

安装logstash和es这些看之前的一篇文章 第一步修改logstash.conf文件 input {jdbc {jdbc_driver_library > "/usr/share/logstash/lib/mysql-connector-j-8.0.33.jar"jdbc_driver_class > "com.mysql.cj.jdbc.Driver"jdbc_connection_string > …

RS-232串口和普通串口介绍

RS-232串口和普通串口的区别主要体现在标准和信号电平的不同,虽然“串口”通常指的是基于串行通信的接口,但不同的串口标准在硬件实现和使用场景上有些不同。 RS-232串口 vs 普通串口的区别 RS-232 是一种具体的串行通信协议标准,而“普通串口”这个词通常是指没有明确标准定…

计算机网络之---TCP报文段

TCP报文段 TCP报文段是TCP协议中传输数据的基本单位。TCP协议基于流控制、顺序控制和错误校验等机制&#xff0c;以确保数据的可靠传输。TCP报文段结构由多个字段组成&#xff0c;每个字段在TCP的工作中都有特定的作用 一个典型的TCP报文段由两部分组成&#xff1a; TCP头部&a…

基于多Agent串联与大模型理解能力的智能背单词系统

基于多Agent串联与大模型理解能力的智能背单词系统 摘要 传统的背单词软件往往采用机械重复的记忆方式,缺乏个性化和趣味性,导致用户学习效率低下。本文将介绍一种基于多Agent串联与大模型理解能力的智能背单词系统。该系统通过多个具备思考、记忆和决策能力的Agent协同工作…

安装yarn时显示npm使用淘宝镜像安装报错

问题描述&#xff1a; npm使用淘宝镜像安装报错 错误原因&#xff1a; 淘宝原镜像域名&#xff08;registry.npm.taobao.org&#xff09;的 HTTPS 证书正式到期&#xff0c;npm 淘宝镜像已经从 registry.npm.taobao.org 切换到了 registry.npmmirror.com。解决方案&#xff1a;…

【JAVA面试】java权限修饰符

在 Java 中&#xff0c;访问权限&#xff08;Access Modifiers&#xff09;用于控制类、方法、成员变量等的访问范围。合理使用访问权限能够增强代码的安全性、封装性以及可维护性。 Java 的访问权限修饰符 Java 提供了以下四种访问权限修饰符&#xff0c;分别是&#xff1a; …

2024机器学习前沿:从大型语言模型到高效计算的技术创新与发展趋势

大型语言模型在多个领域的应用潜力及其推动的技术发展与创新 大型语言模型&#xff08;Large Language Models, LLMs&#xff09;如GPT-4、BERT等&#xff0c;凭借其强大的自然语言理解和生成能力&#xff0c;正在多个领域展现出广泛的应用潜力&#xff0c;推动相关技术的快速…