Maven 依赖范围
最后修改于 2025 年 6 月 10 日
在本文中,我们将展示如何在 Maven 项目中使用不同的依赖范围来控制依赖在构建生命周期中的可用时间和位置。
Maven 依赖范围控制了依赖在构建过程不同阶段的可用性。它们决定了依赖何时包含在编译、测试和运行时执行的类路径中。
依赖范围概述
Maven 提供了六种不同的依赖范围,用于定义依赖何时应该可用
- compile - 在所有阶段都可用(默认范围)
- runtime - 仅在运行时和测试阶段可用
- test - 仅在测试编译和执行期间可用
- provided - 在编译时可用,但不打包
- system - 类似于 provided,但必须指定 JAR 的位置
- import - 仅用于 POM 中的依赖管理
Compile 范围
当未指定范围时,compile 范围是默认范围。具有 compile 范围的依赖项在所有类路径中都可用,并包含在最终的 artifact 中。
<?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-scopes-example</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Compile scope (default) -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.1</version>
<scope>compile</scope>
</dependency>
<!-- Same as above, scope is optional for compile -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</project>
Compile 范围的依赖项在整个应用程序中使用,并与最终的 JAR 或 WAR 文件一起打包。
package com.example;
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
public class CompileExample {
public static void main(String[] args) {
// Using compile scope dependencies
Gson gson = new Gson();
String json = gson.toJson("Hello World");
String padded = StringUtils.leftPad("Maven", 10, '*');
System.out.println("JSON: " + json);
System.out.println("Padded: " + padded);
}
}
compile 范围是 Maven 中依赖项的默认范围。如果未指定范围,Maven 则假定为 compile 范围。
<scope>compile</scope>
指定依赖项在编译、测试和运行时阶段都可用。这是默认范围。
Runtime 范围
具有 runtime 范围的依赖项在编译时不是必需的,但在运行时和测试时需要。
<dependencies>
<!-- Database driver - runtime scope -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Logging implementation - runtime scope -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
<scope>runtime</scope>
</dependency>
<!-- Logging API - compile scope -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
package com.example;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RuntimeExample {
private static final Logger logger = LoggerFactory.getLogger(RuntimeExample.class);
public static void main(String[] args) {
// SLF4J API is compile scope - available at compile time
logger.info("Starting application");
// Logback implementation is runtime scope - loaded at runtime
logger.debug("Debug message");
logger.warn("Warning message");
System.out.println("Check the logs!");
}
}
<scope>runtime</scope>
表明依赖项在编译时不需要,但在运行时需要。常用于数据库驱动程序和日志实现。
Test 范围
具有 test 范围的依赖项仅在测试编译和执行阶段可用。它们不包含在最终的 artifact 中。
<dependencies>
<!-- JUnit for testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- Mockito for mocking -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
<!-- AssertJ for fluent assertions -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.mockito.Mockito;
import static org.assertj.core.api.Assertions.assertThat;
public class CompileExampleTest {
@Test
@DisplayName("Should create JSON string")
public void shouldCreateJsonString() {
// Test using test scope dependencies
CompileExample example = new CompileExample();
// Using AssertJ for assertions
assertThat(example).isNotNull();
// Example of mocking with Mockito
String mockString = Mockito.mock(String.class);
Mockito.when(mockString.length()).thenReturn(10);
assertThat(mockString.length()).isEqualTo(10);
}
@Test
@DisplayName("Should handle string operations")
public void shouldHandleStringOperations() {
String result = "test";
assertThat(result)
.isNotNull()
.hasSize(4)
.startsWith("te")
.endsWith("st");
}
}
<scope>test</scope>
指定依赖项仅用于测试。这些依赖项不包含在最终的应用程序包中。
Provided 范围
具有 provided 范围的依赖项在编译时可用,但预计由运行时环境提供。
<dependencies>
<!-- Servlet API - provided by web container -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- JSP API - provided by web container -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<!-- Lombok - compile time only -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
package com.example;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/hello")
public class ServletExample extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
try (PrintWriter out = response.getWriter()) {
out.println("<html>");
out.println("<head><title>Provided Scope Example</title></head>");
out.println("<body>");
out.println("<h1>Hello from Servlet!</h1>");
out.println("<p>Servlet API is provided by container</p>");
out.println("</body>");
out.println("</html>");
}
}
}
package com.example;
import lombok.Data;
import lombok.Builder;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LombokExample {
private String name;
private int age;
private String email;
public static void main(String[] args) {
// Lombok generates getters, setters, equals, hashCode, toString
LombokExample person = LombokExample.builder()
.name("John Doe")
.age(30)
.email("john@example.com")
.build();
System.out.println("Person: " + person);
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
}
}
<scope>provided</scope>
表明依赖项在编译时是必需的,但将由运行时环境(如 Web 容器或 JVM)提供。
System 范围
system 范围类似于 provided,但要求显式指定本地文件系统上 JAR 文件的路径。
<dependencies>
<!-- System scope dependency - not recommended -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/ojdbc8.jar</systemPath>
</dependency>
</dependencies>
通常不推荐使用 System 范围,因为它会使构建不可移植。相反,请将 JAR 安装到本地仓库或使用公司仓库。
<scope>system</scope>
<systemPath>${project.basedir}/lib/ojdbc8.jar</systemPath>
System 范围要求指定 JAR 文件的确切路径。这使得构建环境依赖且可移植性较差。
Import 范围
import 范围仅用于 dependencyManagement 部分,以从其他 POM 导入依赖管理。
<dependencyManagement>
<dependencies>
<!-- Import Spring Boot dependency management -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.1.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Import JUnit BOM -->
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.10.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Versions managed by imported BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<scope>import</scope> <type>pom</type>
Import 范围允许从其他 POM 文件导入依赖管理,通常与物料清单 (BOM) POM 一起使用。
范围的传递性
当一个依赖项具有自己的依赖项(传递性依赖项)时,Maven 会根据直接依赖项范围和传递性依赖项范围来确定它们的范围。
<dependencies>
<!-- Spring Boot starter has many transitive dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.5</version>
<scope>compile</scope>
</dependency>
<!-- Excluding specific transitive dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>3.1.5</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
理解范围的传递性有助于您控制哪些传递性依赖项包含在您的项目中以及何时可用。
查看依赖范围
您可以使用 Maven 命令来分析和理解项目中的依赖范围。
# View dependency tree with scopes $ mvn dependency:tree # View dependencies by scope $ mvn dependency:list # Analyze dependency scope conflicts $ mvn dependency:analyze # Generate dependency report $ mvn dependency:resolve-sources
这些命令可帮助您了解 Maven 如何解析项目中的依赖项及其范围。
依赖范围的最佳实践
以下是一些有效使用依赖范围的最佳实践
- 对测试框架使用 test 范围 - 将 JUnit、Mockito 和其他测试工具放在 test 范围
- 对容器提供的 API 使用 provided 范围 - Servlet API、EJB API 应为 provided 范围
- 对实现使用 runtime 范围 - 数据库驱动程序和日志实现应为 runtime 范围
- 避免 system 范围 - 改用本地仓库安装
- 对 BOM 使用 import 范围 - 从物料清单 POM 导入依赖管理
- 尽量保持 compile 范围最小化 - 只包含在源代码中直接使用的依赖项
来源
在本文中,我们展示了如何在 Maven 项目中使用不同的依赖范围来控制依赖项的可用时间和位置。
作者
列出所有Java教程。