ZetCode

Maven 依赖范围

最后修改于 2025 年 6 月 10 日

在本文中,我们将展示如何在 Maven 项目中使用不同的依赖范围来控制依赖在构建生命周期中的可用时间和位置。

Maven 依赖范围控制了依赖在构建过程不同阶段的可用性。它们决定了依赖何时包含在编译、测试和运行时执行的类路径中。

依赖范围概述

Maven 提供了六种不同的依赖范围,用于定义依赖何时应该可用

Compile 范围

当未指定范围时,compile 范围是默认范围。具有 compile 范围的依赖项在所有类路径中都可用,并包含在最终的 artifact 中。

pom.xml
<?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 文件一起打包。

src/main/java/com/example/CompileExample.java
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 范围的依赖项在编译时不是必需的,但在运行时和测试时需要。

pom.xml (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>
src/main/java/com/example/RuntimeExample.java
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 中。

pom.xml (test 依赖项)
<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>
src/test/java/com/example/CompileExampleTest.java
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 范围的依赖项在编译时可用,但预计由运行时环境提供。

pom.xml (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>
src/main/java/com/example/ServletExample.java
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>");
        }
    }
}
src/main/java/com/example/LombokExample.java
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 文件的路径。

pom.xml (system 范围 - 不推荐)
<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 导入依赖管理。

pom.xml (import 范围)
<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 如何解析项目中的依赖项及其范围。

依赖范围的最佳实践

以下是一些有效使用依赖范围的最佳实践

来源

Maven 依赖机制 - 参考

在本文中,我们展示了如何在 Maven 项目中使用不同的依赖范围来控制依赖项的可用时间和位置。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。至今,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有Java教程