1. 概述

在日常开发中,我们经常会“先加依赖,回头再删”,结果时间一长,build.gradle 里堆了一堆实际上没用的库。这些冗余依赖不仅拖慢构建速度,还可能带来版本冲突、安全漏洞等隐患。

本文将介绍如何使用 Gradle 的 Nebula Lint 插件,快速识别并清理项目中的未使用依赖。整个过程简单粗暴,适合在 CI/CD 或本地构建中集成。

✅ 核心能力:自动检测 implementationtestImplementation 等配置下的无用依赖
⚠️ 注意:该插件目前仅支持 Groovy DSL 的 build.gradle 文件,Kotlin DSL 暂不支持


2. 环境搭建与配置

我们以 Gradle 5 的多模块项目为例进行演示。

插件应用方式

⚠️ 当前版本仅支持在根项目中统一配置,无法在子模块中单独启用。

在根项目的 build.gradle 中添加插件:

plugins {
    id "nebula.lint" version "16.9.0"
}

description = "Gradle 5 root project"

allprojects {
    apply plugin: "java"
    apply plugin: "nebula.lint"
    gradleLint {
        rules = ['unused-dependency']
    }
    group = "com.baeldung"
    version = "0.0.1"
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"

    repositories {
        jcenter()
    }
}

子模块依赖配置

在子模块 build.gradle 中添加两个依赖:

description = "Gradle Unused Dependencies example"

dependencies {
    implementation('com.google.guava:guava:29.0-jre')
    testImplementation('junit:junit:4.12')
}

并创建一个简单的主类:

public class UnusedDependencies {

    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

此时项目结构已准备就绪,接下来我们开始验证插件效果。


3. 检测场景与报告输出

插件通过分析编译后的 class 文件和依赖树,判断某个依赖是否被实际引用。但实际检测结果受多种因素影响,下面我们通过几个典型场景说明。

3.1 基础场景:未使用的直接依赖

执行检测任务:

$ ./gradlew lintGradle

> Task :lintGradle FAILED
# 省略部分输出

warning   unused-dependency                  this dependency is unused and can be removed
unused-dependencies/build.gradle:6
implementation('com.google.guava:guava:29.0-jre')

✖ 1 problem (0 errors, 1 warning)

To apply fixes automatically, run fixGradleLint, review, and commit the changes.

✅ 结果分析:

  • guava 被标记为未使用,因为当前代码中没有任何地方引用它
  • 插件建议运行 fixGradleLint 自动修复

运行自动修复:

$ ./gradlew fixGradleLint

你会发现 guava 依赖已被自动移除。

现在我们“使用”一下 Guava:

public static void main(String[] args) {
    System.out.println("Hello world");
    useGuava();
}

private static void useGuava() {
    List<String> list = ImmutableList.of("Baeldung", "is", "cool");
    System.out.println(list.stream().collect(Collectors.joining(" ")));
}

再次运行 lintGradle

$ ./gradlew lintGradle

BUILD SUCCESSFUL in 559ms
3 actionable tasks: 1 executed, 2 up-to-date

✅ 检测通过,说明插件能正确识别代码中的实际引用。


3.2 场景:使用了传递性依赖

添加 httpclient 依赖:

dependencies {
    implementation('com.google.guava:guava:29.0-jre')
    implementation('org.apache.httpcomponents:httpclient:4.5.12')
    testImplementation('junit:junit:4.12')
}

并在代码中使用其传递依赖 httpcore 的类:

private static void useHttpCore() {
    SSLContextBuilder.create();
}

执行检测:

$ ./gradlew lintGradle

> Task :lintGradle FAILED

warning   unused-dependency                  one or more classes in org.apache.httpcomponents:httpcore:4.4.13 
are required by your code directly (no auto-fix available)
warning   unused-dependency                  this dependency is unused and can be removed 
unused-dependencies/build.gradle:8
implementation('org.apache.httpcomponents:httpclient:4.5.12')

✖ 2 problems (0 errors, 2 warnings)

❌ 问题分析:

  1. httpclient 被标记为未使用(因为我们没直接用它)
  2. httpcore 被提示“应直接声明”,但无法自动修复

查看依赖树确认:

$ ./gradlew unused-dependencies:dependencies --configuration compileClasspath

输出节选:

\--- org.apache.httpcomponents:httpclient:4.5.12
     +--- org.apache.httpcomponents:httpcore:4.4.13
     +--- commons-logging:commons-logging:1.2
     \--- commons-codec:commons-codec:1.11

✅ 最佳实践:
如果你的代码直接引用了某个传递依赖的类,应该将其提升为直接依赖,避免因上游库变更导致构建失败。

修正方式:

dependencies {
    implementation('com.google.guava:guava:29.0-jre')
    implementation('org.apache.httpcomponents:httpcore:4.4.13') // 显式声明
    testImplementation('junit:junit:4.12')
}

3.3 场景:通过反射使用的依赖

修改代码,使用反射调用 HttpClientBuilder

private static void useHttpClientWithReflection() {
    try {
        Class<?> httpBuilder = Class.forName("org.apache.http.impl.client.HttpClientBuilder");
        Method create = httpBuilder.getMethod("create", null);
        create.invoke(httpBuilder, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

再次运行 lintGradle,结果依然是:

warning   unused-dependency  ... httpclient:4.5.12

⚠️ 踩坑点:
Nebula Lint 无法检测通过反射使用的类。静态分析工具无法追踪 Class.forName 这类动态行为。

✅ 解决方案:

  • 将此类依赖改为 runtimeOnly,明确表示编译期不引用
  • 或在插件配置中排除特定依赖(不推荐)
dependencies {
    runtimeOnly 'org.apache.httpcomponents:httpclient:4.5.12'
}

这样既保留运行时依赖,又避免误报。


3.4 生成检测报告

对于大型项目,终端输出的警告信息难以管理。我们可以让插件生成结构化报告。

修改根项目配置:

allprojects {
    apply plugin: "java"
    apply plugin: "nebula.lint"
    gradleLint {
        rules = ['unused-dependency']
        reportFormat = 'text' // 可选: 'html', 'xml'
    }
    // 其他配置...
}

生成报告:

$ ./gradlew generateGradleLintReport

查看报告内容:

$ cat unused-dependencies/build/reports/gradleLint/unused-dependencies.txt

输出示例:

CodeNarc Report - Jun 20, 2020, 3:25:28 PM

Summary: TotalFiles=1 FilesWithViolations=1 P1=0 P2=3 P3=0

File: /home/user/tutorials/gradle-5/unused-dependencies/build.gradle
    Violation: Rule=unused-dependency P=2 Line=null Msg=[one or more classes in org.apache.httpcomponents:httpcore:4.4.13 
                                                         are required by your code directly]
    Violation: Rule=unused-dependency P=2 Line=9 Msg=[this dependency is unused and can be removed] 
                                                 Src=[implementation('org.apache.httpcomponents:httpclient:4.5.12')]
    Violation: Rule=unused-dependency P=2 Line=17 Msg=[this dependency is unused and can be removed] 
                                                  Src=[testImplementation('junit:junit:4.12')]

✅ 报告优势:

  • 可集成到 CI 流程,失败时阻断构建
  • 支持 texthtmlxml 多种格式,便于自动化解析
  • 同时检测 testCompileClasspath,发现未使用的测试依赖

⚠️ 注意:插件对测试依赖的检测行为有时不稳定,建议结合 ./gradlew dependencies 手动验证。


4. 总结

通过 Nebula Lint 插件,我们可以高效识别 Gradle 项目中的冗余依赖,提升构建效率与项目整洁度。

关键要点回顾:

场景 检测结果 建议处理方式
未使用的直接依赖 ✅ 能检测 运行 fixGradleLint 自动删除
使用传递依赖 ✅ 能检测 显式声明为直接依赖
反射使用依赖 ❌ 无法检测 改为 runtimeOnly 避免误报
大项目管理 ✅ 支持报告 使用 generateGradleLintReport

📌 最后提醒:
该插件是静态分析工具,不能 100% 替代人工审查。建议在项目重构、技术债清理时使用,定期运行以保持依赖清爽。

完整示例代码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/gradle-modules/gradle-5


原始标题:Finding Unused Gradle Dependencies