Maven 依赖解析
最后修改于 2025 年 6 月 10 日
在本文中,我们将展示 Maven 如何解析依赖以及管理依赖树。Maven 的依赖解析机制会自动处理直接依赖、传递依赖和版本冲突。
Maven 依赖解析是 Maven 确定编译、测试和运行时所需的工件(JAR、WAR 等)的过程。它分析项目的依赖及其传递依赖,以创建完整的类路径。
传递依赖是您声明的依赖的依赖。Maven 会自动解析这些依赖,确保所有必需的库都可用,而无需您显式声明它们。此功能简化了依赖管理,并减少了手动配置的需求。
基本依赖声明
让我们从一个基本示例开始,该示例演示了如何声明依赖以及理解 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>dependency-example</artifactId>
<version>1.0.0</version>
<packaging>jar</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>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
此基本配置声明了三个依赖:Apache Commons Lang3、Jackson Databind 和 JUnit。Maven 将自动解析并下载这些依赖及其传递依赖。
理解传递依赖
当您声明一个依赖时,Maven 会自动包含其传递依赖。例如,jackson-databind 依赖于 jackson-core 和 jackson-annotations,Maven 会自动下载它们。
package com.example;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.HashMap;
import java.util.Map;
public class DependencyApp {
public static void main(String[] args) throws Exception {
System.out.println("Maven Dependency Resolution Example");
// Using Apache Commons Lang3
String text = " Hello Maven Dependencies ";
String trimmed = StringUtils.strip(text);
System.out.println("Trimmed text: '" + trimmed + "'");
// Using Jackson for JSON processing
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> data = new HashMap<>();
data.put("project", "Maven Dependency Example");
data.put("dependencies", new String[]{"commons-lang3", "jackson"});
data.put("resolved", true);
String json = mapper.writeValueAsString(data);
System.out.println("JSON output: " + json);
// Parse back
JsonNode node = mapper.readTree(json);
System.out.println("Project name: " + node.get("project").asText());
}
}
此 Java 应用程序使用声明的依赖项来演示 Maven 如何解析和管理它们。它使用 Apache Commons Lang3 进行字符串操作,使用 Jackson 进行 JSON 处理。当您运行此应用程序时,Maven 将自动下载所需的依赖项及其传递依赖项。
在没有 exec 插件声明的情况下运行
这是一个有趣的 Maven 功能:即使 exec 插件未在 pom.xml 中声明,您也可以使用它来运行主类。Maven 会在需要时自动下载并使用该插件。
$ mvn compile $ mvn exec:java -Dexec.mainClass="com.example.DependencyApp"
Maven 将自动下载 exec 插件及其依赖项,然后执行您的主类。这展示了 Maven 的插件解析机制与依赖解析并行工作。
[INFO] --- exec-maven-plugin:3.1.0:java (default-cli) @ dependency-example ---
Maven Dependency Resolution Example
Trimmed text: 'Hello Maven Dependencies'
JSON output: {"project":"Maven Dependency Example","dependencies":["commons-lang3","jackson"],"resolved":true}
Project name: Maven Dependency Example
依赖树分析
Maven 提供了用于分析和理解依赖树的工具。依赖树显示了所有已解析的依赖项,包括传递依赖项。
$ mvn dependency:tree
此命令显示项目的完整依赖树
[INFO] com.example:dependency-example:jar:1.0.0 [INFO] +- org.apache.commons:commons-lang3:jar:3.14.0:compile [INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:compile [INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:compile [INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:compile [INFO] \- junit:junit:jar:4.13.2:test [INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
该树显示 jackson-databind 引入了两个传递依赖:jackson-annotations 和 jackson-core。JUnit 作为传递依赖引入了 hamcrest-core。
分析特定依赖
您还可以分析特定工件的依赖
$ mvn dependency:tree -Dincludes=com.fasterxml.jackson.core
这仅显示树中的 Jackson 相关依赖,帮助您理解特定的依赖链。
依赖范围和解析
Maven 支持不同的依赖范围,这些范围会影响依赖的可用时间和解析方式。
<dependencies>
<!-- Compile scope (default) - available everywhere -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
<scope>compile</scope>
</dependency>
<!-- Runtime scope - available at runtime and testing -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>runtime</scope>
</dependency>
<!-- Test scope - only available during testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<!-- Provided scope - available at compile time but not packaged -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
Maven 支持多种依赖范围,这些范围决定了依赖的解析和包含方式。最常见的范围是:compile、runtime、test 和 provided。
<scope>compile</scope>
默认范围。依赖项在所有类路径中都可用,并包含在最终的工件中。用于编译和运行时所需的依赖项。
<scope>runtime</scope>
编译时不需要,但在运行时必需的依赖项。通常用于数据库驱动程序或日志实现。
<scope>test</scope>
仅在测试编译和执行期间可用的依赖项。不包含在最终的工件中。用于测试框架和实用程序。
<scope>provided</scope>
在编译时可用但在打包时不包含的依赖项。预期由运行时环境提供(例如 Web 容器中的 servlet API)。
版本冲突解析
当依赖树中找到同一依赖的多个版本时,Maven 使用特定规则来解析冲突。
<dependencies>
<!-- Direct dependency on commons-lang3 3.14.0 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<!-- This dependency might require commons-lang3 3.12.0 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
Maven 使用“最近者获胜”策略解析版本冲突:选择在依赖树中离您的项目最近的版本。直接依赖优先于传递依赖。
分析版本冲突
使用依赖插件识别版本冲突
$ mvn dependency:tree -Dverbose
这会显示被省略的依赖项,并解释为什么选择了某些版本
[INFO] +- org.apache.commons:commons-lang3:jar:3.14.0:compile [INFO] +- org.apache.commons:commons-configuration2:jar:2.10.1:compile [INFO] | +- (org.apache.commons:commons-lang3:jar:3.12.0:compile - omitted for conflict with 3.14.0)
显式版本管理
使用依赖管理来显式控制整个项目的版本。这在多模块项目中尤其有用,您希望确保所有模块的版本一致。
dependencyManagement 标签允许您在一个地方定义依赖的版本,然后可以在多个模块中引用这些版本,而无需每次都指定版本。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Version managed by dependencyManagement -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
在我们的例子中,dependencyManagement 部分定义了 commons-lang3 和 jackson-databind 的版本。当我们声明这些依赖项在 dependencies 部分而不指定版本时,Maven 会使用 dependencyManagement 中定义 的版本。
排除传递依赖
有时您需要排除特定的传递依赖,以避免冲突或减少依赖的占用空间。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.1.8</version>
<exclusions>
<!-- Exclude commons-logging to use SLF4J instead -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Provide SLF4J implementation instead -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
</dependencies>
此配置从 Spring 中排除 commons-logging,并用 SLF4J 桥接替换它,从而提供更好的日志控制并避免冲突。
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
排除项可防止包含特定的传递依赖项。只需要 groupId 和 artifactId;在排除项中不指定版本。
插件解析和自动下载
Maven 会自动解析和下载插件,即使它们未显式声明。这包括常用的插件,如用于运行 Java 应用程序的 exec 插件。
<!-- No exec plugin declared in pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
即使没有声明 exec 插件,您仍然可以使用它
$ mvn exec:java -Dexec.mainClass="com.example.DependencyApp"
Maven 将输出类似以下内容
[INFO] Downloading from central: https://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/3.1.0/exec-maven-plugin-3.1.0.pom [INFO] Downloaded from central: https://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/3.1.0/exec-maven-plugin-3.1.0.pom (11 kB) [INFO] Downloading from central: https://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/3.1.0/exec-maven-plugin-3.1.0.jar [INFO] Downloaded from central: https://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/3.1.0/exec-maven-plugin-3.1.0.jar (61 kB) [INFO] --- exec-maven-plugin:3.1.0:java (default-cli) @ dependency-example ---
这演示了 Maven 的插件解析机制如何自动工作,在需要时提供功能。
显式插件配置
对于生产项目,最好显式配置插件
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>com.example.DependencyApp</mainClass>
<cleanupDaemonThreads>false</cleanupDaemonThreads>
</configuration>
<executions>
<execution>
<id>run-app</id>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
此配置显式声明了 exec-maven-plugin 并指定了要运行的主类。它确保插件始终可用并配置正确,从而避免了自动解析的潜在问题。
executions 标签允许您定义在构建生命周期期间运行插件的特定目标。在这种情况下,我们定义了一个 ID 为 run-app 的执行,它运行 exec 插件的 java 目标。这使得可以使用简单的命令更轻松地运行您的应用程序
$ mvn exec:java -Dexec.mainClass="com.example.DependencyApp"
此命令将执行插件配置中指定的主类,确保正确运行类而无需每次都指定它。
通过显式配置,您可以简单地运行
$ mvn exec:java
插件将自动使用配置的主类。
依赖解析故障排除
Maven 提供了多种工具来排除依赖解析问题。
有效 POM 分析
查看有效 POM 以了解 Maven 如何解析您的配置
$ mvn help:effective-pom
这显示了在解析了所有继承、配置文件和属性之后的完整 POM。
依赖分析
分析您的依赖项,找出未使用的或未声明的依赖项
$ mvn dependency:analyze
此命令识别
[WARNING] Used undeclared dependencies found: [WARNING] com.fasterxml.jackson.core:jackson-core:jar:2.17.1:compile [WARNING] Unused declared dependencies found: [WARNING] org.apache.commons:commons-lang3:jar:3.14.0:compile
分析有助于通过识别未使用的直接依赖项或直接使用的代码缺少声明等问题来优化您的依赖项声明。
解析依赖项来源
下载源 JAR 以实现更好的 IDE 集成和调试
$ mvn dependency:sources
这会下载所有依赖项的源附件,从而在 IDE 中更容易地调试和理解第三方代码。
存储库配置
Maven 从存储库解析依赖项。您可以为特定依赖项配置自定义存储库。
<repositories>
<repository>
<id>central</id>
<name>Maven Central Repository</name>
<url>https://repo1.maven.org/maven2</url>
<layout>default</layout>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Release Repository</name>
<url>https://repo.spring.io/release</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>https://repo1.maven.org/maven2</url>
<layout>default</layout>
</pluginRepository>
</pluginRepositories>
存储库配置允许 Maven 从多个源解析依赖项,包括私有存储库或其他公共存储库。
高级依赖功能
Maven 支持高级依赖功能以应对复杂场景。
可选依赖
可选依赖项对于您项目的主要功能不是必需的,但如果需要可以包含它们。它们以 optional 范围声明,这可以防止它们被传递包含到依赖您工件的项目中。
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
<optional>true</optional>
</dependency>
</dependencies>
当其他项目依赖您的工件时,可选依赖项不会被传递包含。消费者如果需要,必须显式声明它们。
系统依赖
系统依赖项用于包含不在任何远程存储库中的 JAR 文件。它们通常用于 Maven 未管理的本地库或系统 JAR。
<dependencies>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.3.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/ojdbc8.jar</systemPath>
</dependency>
</dependencies>
系统依赖项需要指定本地 JAR 文件路径,这会使构建的便携性降低。它们与 provided 范围类似,但由于安装依赖项到本地或公司存储库而不再推荐使用。
<scope>system</scope>
<systemPath>${project.basedir}/lib/ojdbc8.jar</systemPath>
系统范围的依赖项依赖于特定的文件路径,如果文件不可用或跨环境的路径不同,可能会破坏构建。
物料清单 (BOM) 和 import 范围
import 范围在 dependencyManagement 部分使用,以从物料清单 (BOM) POM 导入依赖项配置,从而确保依赖项的版本一致。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
import 范围通过从 BOM(如 Spring Boot 的)继承依赖项版本来简化版本管理,确保兼容性。
<scope>import</scope> <type>pom</type>
仅在 dependencyManagement 中使用,用于从另一个 POM 导入版本化的依赖项配置。
依赖解析最佳实践
以下是一些优化 Maven 依赖解析的最佳实践
- 使用 dependencyManagement 进行版本控制 - 集中版本定义,以确保多模块项目之间的一致性。
- 最小化传递依赖 - 使用排除项删除不必要的或冲突的依赖项。
-
定期分析依赖项 - 运行
mvn dependency:tree和mvn dependency:analyze来识别问题。 - 避免使用 system 范围 - 将依赖项安装到存储库,而不是使用本地文件路径。
- 为复杂项目使用 BOM - 导入 Spring Boot 或 JUnit 等 BOM,以简化依赖管理。
-
保持依赖项最新 - 使用
mvn versions:display-dependency-updates检查新版本。 - 显式声明插件 - 在 POM 中配置 exec-maven-plugin 等插件,以实现可预测的构建。
来源
在本文中,我们展示了 Maven 如何解析依赖,处理传递依赖,解析版本冲突,并提供用于故障排除和优化的工具。
作者
列出所有Java教程。