You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

引用可执行Jar子目录内Jar类时出现ClassNotFoundException

问题描述

现有两个基于Gradle的Java项目,均需打包为.jar文件:

1. Core项目

  • 类型为java-library
  • 打包后的Jar包含项目自身的.class文件,内部lib目录存放所有第三方依赖.jar文件
  • Jar任务配置如下:
tasks.getByName<Jar>("jar") {
    enabled = true
    archiveClassifier.value("api")

    from(sourceSets.main.get().output)
    into("lib") {
        from(configurations.runtimeClasspath)
    }
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

2. Import项目

  • 用于命令行执行
  • 打包后的Jar包含自身.class文件,内部lib目录存放第三方依赖.jar文件(与core.jar://lib/*.jar一致)
  • 构建脚本中依赖Core的方式:
dependencies {
    implementation group: 'com.company.core', name: 'core', version: '0.1.0-SNAPSHOT', classifier: 'api'
}
  • Jar任务配置如下:
jar {
    zip64 = true
    manifest {
        attributes 'Main-Class': 'com.import.Runner'
    }
    from {
        duplicatesStrategy(DuplicatesStrategy.INCLUDE)

        configurations.runtimeClasspath.collect {

            if (it.isDirectory()) {
                println "Directory: $it" // 从未打印
                it
            } else {
                println "Zip Tree: $it" // 会打印,显示本地maven目录中的jar路径
                zipTree(it)
            }
        }
    }

    exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
}

执行异常

执行Import的Jar时,尝试以下命令:

java -cp import.jar com.import.Runner

// 也曾尝试:
java -cp "import.jar:lib/*" com.import.Runner

均出现如下异常:

Exception in thread "main" java.lang.NoClassDefFoundError: com/company/exception/ApplicationException
    at com.import.ImportClient.<init>(ImportClient.java:24)
Caused by: java.lang.ClassNotFoundException: com.company.exception.ApplicationException

疑问

  1. JVM为何无法加载可执行Jar内部lib目录下第三方Jar中的类?
  2. 指定-cp "import.jar:lib/*"为何对Jar内部的lib目录不生效?

问题解答

核心原因

JVM的类加载器默认不会识别Jar文件内部嵌套的Jar包:

  • 当你把依赖Jar打包到import.jar的lib目录下时,这些嵌套Jar只是主Jar里的普通文件,JVM不会自动扫描并加载其中的类。
  • 你尝试的-cp "import.jar:lib/*"中的lib/*,指向的是当前操作系统目录下的lib文件夹,而非import.jar内部的lib目录,因此这个参数完全不会作用到主Jar里的嵌套依赖。

解决方案

方案1:使用Gradle Shadow插件(推荐)

Shadow插件可以自动处理嵌套依赖,生成可直接运行的Fat Jar,或保留嵌套结构并配置类路径:

  1. 在Import项目的build.gradle中引入插件:
plugins {
    id 'java'
    id 'com.github.johnrengelman.shadow' version '8.1.1' // 可替换为最新版本
}
  1. 配置ShadowJar任务:
shadowJar {
    zip64 = true
    manifest {
        attributes 'Main-Class': 'com.import.Runner'
        // 配置类路径指向内部lib目录的依赖
        attributes 'Class-Path': configurations.runtimeClasspath.files.collect { "lib/${it.name}" }.join(' ')
    }
    // 将依赖复制到Jar内部的lib目录
    into('lib') {
        from configurations.runtimeClasspath
    }
    exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
  1. 生成并运行Jar:
# 生成可执行Jar
./gradlew shadowJar
# 运行Jar
java -jar build/libs/import-<version>-all.jar

方案2:手动配置Manifest的Class-Path

若不想用插件,可修改Import的Jar任务,在Manifest中显式声明内部依赖路径:

jar {
    zip64 = true
    manifest {
        attributes 'Main-Class': 'com.import.Runner'
        // 生成Class-Path属性,指向Jar内部lib目录的所有依赖
        attributes 'Class-Path': configurations.runtimeClasspath.files.collect { "lib/${it.name}" }.join(' ')
    }
    // 将依赖复制到Jar内部的lib目录
    into('lib') {
        from configurations.runtimeClasspath
    }
    exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

注意:这种方式下,import.jar必须和内部lib目录的结构保持一致(不能单独移动Jar文件),否则相对路径会失效。

方案3:合并所有类到主Jar中

修改Import的Jar任务,仅提取依赖中的类(排除Core Jar自带的lib目录),合并到主Jar:

jar {
    zip64 = true
    manifest {
        attributes 'Main-Class': 'com.import.Runner'
    }
    from {
        duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
        configurations.runtimeClasspath.collect {
            if (it.name.contains('core-api')) {
                // 只取Core Jar中的自身类,排除它的lib目录
                zipTree(it).matching { exclude 'lib/**' }
            } else {
                zipTree(it)
            }
        }
    }
    exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
}

生成Jar后,直接用java -cp import.jar com.import.Runner即可运行。


内容的提问来源于stack exchange,提问作者bobbyrne01

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 03:24:54