使用Gradle运行时出现NoClassDefFoundError错误

25

我正在使用Gradle作为JavaFX插件。在构建和运行可执行文件时,即使在distribution/中,一切都工作得非常完美,除了一个类:CloseableHttpClient

出于几个目的,我按以下方式创建了以下对象:

CloseableHttpClient client = HttpClients.createDefault();

在IDE中运行程序没有问题,一切都正常工作。但如果我构建并尝试运行.exe文件,则会出现以下Throwable堆栈跟踪:

在IDE中运行程序没有问题,一切都正常工作。但如果我构建并尝试运行.exe文件,则会出现以下Throwable堆栈跟踪:

java.lang.NoClassDefFoundError: Could not initialize class org.apache.http.conn.ssl.SSLConnectionSocketFactory
    at org.apache.http.impl.client.HttpClientBuilder.build(HttpClientBuilder.java:955)
    at org.apache.http.impl.client.HttpClients.createDefault(HttpClients.java:58)
    at ch.itcb.tools.lom.util.JsonSimpleUtil.http(JsonSimpleUtil.java:29)...

我真的不明白这个。为什么只有这个类找不到,但我的其他所有类都可以找到呢?

我的 build.gradle 文件:

apply plugin: 'java'
apply plugin: 'eclipse'
apply from: 'javafx.plugin'

sourceCompatibility = 1.8
version = '0.1'

jar {
    manifest {
        attributes 'Implementation-Title': 'LogoffManager',
                   'Implementation-Version': version
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile fileTree(dir: 'lib', include: ['*.jar'])

    compile 'ch.qos.logback:logback-classic:1.1.3'

    compile 'org.apache.httpcomponents:httpclient:4.5.1'

    compile 'com.googlecode.json-simple:json-simple:1.1'



    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

test {
    systemProperties 'property': 'value'
}

uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}
请在需要更多信息的情况下发表评论。谢谢。

请分享您的build.gradle文件。 - Opal
@Opal 完成了。仍然没有找到解决方法 :/ - jntme
发布版中是否包含Apache Jar文件?它是否包含缺失的类? - Jens Schauder
@JensSchauder 是的,它确实可以。我自己都不敢相信。 - jntme
没有更多的帮助了吗?仍然在为同样的问题苦苦挣扎。。 - jntme
1
你找到解决方案了吗?你愿意分享吗?谢谢。 - delkant
3个回答

31

这是一个很好的问题,我在研究Java开发人员可能遇到的类路径问题的示例时刚刚遇到了它 :-)

我从您的build.gradle的最小版本开始(仅包括直接相关的内容),具体来说:

plugins {
    id 'java'
}
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

jar {
    manifest {
        attributes 'Main-Class': 'com.oliverlockwood.Main'
    }
}

dependencies {
    compile 'org.apache.httpcomponents:httpclient:4.5.1'
}

在这个上下文中,我的 'Main' 类使用了你的代码示例,即:

package com.oliverlockwood;

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class Main {
    public static void main(String[] args) {
        CloseableHttpClient client = HttpClients.createDefault();
    }
}

在这个阶段,我可以运行gradle clean build,然后运行java -jar build/libs/33106520.jar(我的项目是以这个StackOverflow问题命名的),我会看到这个:

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/impl/client/HttpClients
    at com.oliverlockwood.Main.main(Main.java:8)
Caused by: java.lang.ClassNotFoundException: org.apache.http.impl.client.HttpClients
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

这与你的错误略有不同,但在我们深入挖掘和重现之前,让我强调一件事情:无论是你看到的这个错误还是那个错误,在运行时都是由于类加载器无法找到所需的类而引起的。这里有一篇相当不错的博客文章(链接),介绍了编译时类路径和运行时类路径之间的区别。

如果我运行gradle dependencies,我可以看到项目的运行时依赖项:

runtime - Runtime classpath for source set 'main'.
\--- org.apache.httpcomponents:httpclient:4.5.1
     +--- org.apache.httpcomponents:httpcore:4.4.3
     +--- commons-logging:commons-logging:1.2
     \--- commons-codec:commons-codec:1.9

我一个一个地手动将它们添加到运行时类路径中。 (值得一提的是,这通常不被认为是良好的做法;但出于实验的目的,我将这些jar文件复制到我的build/libs文件夹中,并使用java -cp build/libs/33106520.jar:build/libs/* com.oliverlockwood.Main运行。有趣的是,这无法重现您确切的问题。总结一下:

  • 如果在运行时未提供org.apache.httpcomponents:httpclient,则我们会失败,因为找不到HttpClients jar。
  • 如果在运行时提供了org.apache.httpcomponents:httpclient:4.5.1,则您的问题不会出现——请注意,构建失败找不到的类(org.apache.http.conn.ssl.SSLConnectionSocketFactory)是此相同Apache库的一部分,这非常可疑。

因此,我怀疑您的运行时类路径包含不同版本的Apache httpclient库。由于有很多版本,我不会测试每个组合,因此我将向您提供以下建议。

  1. 如果您想全面了解问题的根本原因,请确定错误情况的运行时类路径中存在哪些jar(包括它们的版本),包括如果您正在创建fat jar中打包的任何jar。如果您能在此共享这些详细信息,那将非常好;根本原因分析通常有助于每个人更好地理解:-)
  2. 在可能的情况下,避免以compile fileTree(dir:'lib',include:['*.jar'])的方式使用依赖项。基于Maven或JCenter等存储库的托管依赖项比随机目录中的依赖项更容易一致地使用。如果这些是您不想发布到开源工件存储库的内部库,则可以考虑设置一个本地Nexus实例或类似的。
  3. 考虑生成"fat jar"而不是"thin jar"——这意味着所有运行时依赖项都打包在您构建的jar中。我建议使用Gradle的Shadow插件——通过在我的build.gradle中安装它,并运行gradle clean shadow,我能够在不需要手动添加任何内容到我的类路径的情况下成功运行java -jar

我遇到了 java.lang.NoClassDefFoundError: org/apache/http/config/Lookup 的错误。我在 Eclipse 的 Maven Dependencies 下的 Apache Http JARs (httpclient, httpcore) 中一直寻找这个类的存在,但没有找到。后来意识到 Maven 选择从父 POM 中解析某个其他版本的 httpcore 而不是 httpclient 直接依赖的版本。因此,我不得不显式地指定一个与 httpclient 直接依赖的版本相匹配的 httpcore 版本。 - user104309
1
在最近的Gradle版本中,gradle dependencies 会产生一条消息:通过添加 --scan 选项,可以获得基于Web的可搜索依赖关系报告。gradle build --scan 则会生成一个公开浏览的报告,其中包含您可以在完成后删除的依赖项。 - Eugene Gr. Philippov
谢谢您提到依赖关系的提示 - 我有一个宇宙动物园的版本,导致了NoClassDef* - Eugene Gr. Philippov
1
切换到使用 IJ 构建的 Gradle 设置,目前解决了问题。 - Vinz243

0
对于Spring Boot用户,这个问题可以用一行代码解决。我正在使用Gradle/Kotlin,所以:

id("org.springframework.boot") version "2.5.5"

将此行代码放在你的build.gradle.kts文件的plugins {}部分中。

欲了解更多信息,请访问Spring Boot Gradle插件参考指南


0
对于我的情况,我在三个月后打开了我的InteliJ,遇到了一些运行时错误,比如noclassdeffounderror。我不得不***刷新gradle***,然后这些错误就消失了。

如何刷新Gradle? - Keverly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接