Java 使用 Gradle 在运行时出现 NoClassDefFoundError

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33106520/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 14:20:10  来源:igfitidea点击:

NoClassDefFoundError at Runtime with Gradle

javagradlenoclassdeffounderrorthrowable

提问by jntme

I'm using gradle as the JavaFX plugin. Everything works perfectly even after building and runnig the excecutable at distribution/, except with one class: CloseableHttpClient

我使用 gradle 作为 JavaFX 插件。即使在distribution/处构建并运行可执行文件后,一切都运行良好,除了一个类:CloseableHttpClient

For several purposes I create the following object like this:

出于多种目的,我创建了如下对象:

CloseableHttpClient client = HttpClients.createDefault();

Running the program in the IDE is no problem, everything works fine. But if I build and try to run the .exe-File I get the following Throwable-StackTrace:

在 IDE 中运行程序没有问题,一切正常。但是,如果我构建并尝试运行 .exe 文件,我会得到以下Throwable-StackTrace:

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)...

I really don't understand that. How can it be that just this class doesn't get found, but all my other classes do?

我真的不明白。怎么可能只有这个类没有被找到,而我所有的其他类都找到了?

My build.gradle file:

我的 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'
       }
    }
}

Please write a comment if you need more information. Thx.

如果您需要更多信息,请发表评论。谢谢。

采纳答案by Olaf

it's a good question, which I came across just now while researching examples of the many ways Java developers can end up with class path fun :-)

这是一个很好的问题,我刚刚在研究 Java 开发人员最终获得类路径乐趣的多种方式的示例时遇到了这个问题:-)

I started with a minimal version of your build.gradle (including only what's directly relevant), specifically:

我从您的 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'
}

My 'Main' class, in this context, uses your code example, i.e.:

在这种情况下,我的“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();
    }
}

At this stage, I can run gradle clean buildfollowed by java -jar build/libs/33106520.jar(my project was named after this StackOverflow question) and I see this:

在这个阶段,我可以运行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)

This is subtly different from your error, but before we dig and reproduce that, let me emphasise something: both this error and the one you're seeing are caused at runtime when the classloader is unable to find a class that it needs. There's quite a good blog post herewith some more details about the difference between compile-time classpath and runtime classpaths.

这与您的错误略有不同,但在我们挖掘和重现之前,让我强调一点:这个错误和您看到的错误都是在运行时类加载器无法找到它需要的类时引起的。有相当不错的博客文章在这里约编译时类路径和运行时类路径之间的区别的更多细节。

If I run gradle dependenciesI can see the runtime dependencies for my project:

如果我运行,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

I added these manually one-by-one to my runtime classpath. (For the record, this isn't generally considered good practice; but for the sake of the experiment, I copied these jars to my build/libsfolder and ran with java -cp build/libs/33106520.jar:build/libs/* com.oliverlockwood.Main. Interestingly enough, this wasn't able to reproduce your exact problem. To recap:

我将这些手动一一添加到我的运行时类路径中。(为了记录,这通常不被认为是好的做法;但为了实验,我将这些 jars 复制到我的build/libs文件夹并运行java -cp build/libs/33106520.jar:build/libs/* com.oliverlockwood.Main。有趣的是,这无法重现您的确切问题。回顾一下:

  • Without org.apache.httpcomponents:httpclientavailable at runtime, then we fail because the HttpClientsjar is not found.
  • With org.apache.httpcomponents:httpclient:4.5.1available at runtime, then your problem does not manifest - and I note that the class your build fails to find (org.apache.http.conn.ssl.SSLConnectionSocketFactory) is part of this same Apache library, which is very suspicious indeed.
  • 如果org.apache.httpcomponents:httpclient在运行时不可用,那么我们将失败,因为HttpClients找不到 jar。
  • 随着org.apache.httpcomponents:httpclient:4.5.1在运行时可用,那么你的问题并不明显-我注意到,你的类构建失败找(org.apache.http.conn.ssl.SSLConnectionSocketFactory)是这同一个Apache库的一部分,这是非常可疑的确实。

My suspicion is then that your runtime classpath contains a different versionof the Apache httpclient library. Since there's a whole lotta versions out there, I'm not going to test every single combination, so I will instead leave you with the following advice.

我怀疑您的运行时类路径包含不同版本的 Apache httpclient 库。由于有很多版本,我不会测试每一个组合,所以我会给你以下建议。

  1. If you want to fully understand the root cause of your issue, then identify exactlywhich jars (including their versions) are present in your error-case runtime classpath, including any jars that are packaged inside yours if you're creating a fat jar (more on this in point 3). It'd be great if you shared these details here; root cause analysis usually helps everyone to understand better :-)
  2. Where possible, avoid using dependencies in the manner of compile fileTree(dir: 'lib', include: ['*.jar']). Managed dependencies based on a repository such as Maven or JCenter are much easier to work with consistently than dependencies in a random directory. If these are internal libraries that you don't want to publish to an open-source artifact repository, then it may be worth setting up a local Nexus instance or similar.
  3. Consider producing a "fat jar" instead of a "thin jar" - this means that all runtime dependencies are packaged in the jar that you build. There's a good Shadow plugin for Gradlethat I'd recommend - with this in place in my build.gradle, and running gradle clean shadow, I was able to run java -jarjust fine without needing to manually add anything to my classpath.
  1. 如果您想完全了解问题的根本原因,请准确确定错误案例运行时类路径中存在哪些 jar(包括它们的版本),如果您正在创建一个胖 jar,则包括打包在您的内部的任何 jar(在第 3 点中有更多关于这一点的内容)。如果你能在这里分享这些细节,那就太好了;根本原因分析通常可以帮助每个人更好地理解:-)
  2. 在可能的情况下,避免以compile fileTree(dir: 'lib', include: ['*.jar']). 基于存储库(例如 Maven 或 JCenter)的托管依赖项比随机目录中的依赖项更易于一致地使用。如果这些是您不想发布到开源工件存储库的内部库,那么可能值得设置本地 Nexus 实例或类似实例。
  3. 考虑生成“胖 jar”而不是“瘦 jar”——这意味着所有运行时依赖项都打包在您构建的 jar 中。有一个很好的阴影插件的摇篮,我建议你-与此代替我build.gradle,并运行gradle clean shadow,我是能够运行java -jar而无需手动添加任何东西到我的类路径就好了。