web-dev-qa-db-ja.com

Gradleを使用した実行時のNoClassDefFoundError

JavaFXプラグインとしてgradleを使用しています。 1つのクラスを除いて、distribution /で実行可能ファイルを構築して実行した後でも、すべてが完全に機能します:CloseableHttpClient

いくつかの目的で、次のようなオブジェクトを作成します。

CloseableHttpClient client = HttpClients.createDefault();

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

私は本当にそれを理解していません。このクラスだけが見つからないのに、他のすべてのクラスが見つかるのはどうしてですか?

私の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'
       }
    }
}

さらに情報が必要な場合は、コメントを書いてください。 THX。

13
jntme

それは良い質問です。多くの方法の例を研究しているときに私がたった今遭遇しました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'
}
_

このコンテキストでの私の「メイン」クラスは、コード例を使用します。

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

これはエラーとは微妙に異なりますが、それを掘り下げて再現する前に、強調してみましょう:このエラーと表示されているエラーの両方は、クラスローダーが必要なクラスを見つけることができないときに実行時に発生します。かなり良いブログ投稿があります here コンパイル時のクラスパスと実行時のクラスパスの違いについての詳細があります。

_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
_

これらをランタイムクラスパスに1つずつ手動で追加しました。 (記録的には、これは一般的に良い習慣とは見なされませんが、実験のために、これらの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の部分ですlibrary、これは非常に疑わしいものです。

私の疑いは、ランタイムクラスパスに異なるバージョンのApache httpclientライブラリが含まれていることです。たくさんのバージョンがあるので、すべての組み合わせをテストするつもりはありません。そのため、代わりに次のアドバイスをお伝えします。

  1. 問題の根本原因を完全に理解する場合は、エラーケースランタイムクラスパスに存在するjar(バージョンを含む)をexactly識別します、ファットjarを作成する場合は、内部にパッケージ化されているjarを含めます(これについては、ポイント3で詳しく説明します)。ここでこれらの詳細を共有していただければ幸いです。根本原因分析は通常、誰もがよりよく理解するのに役立ちます:-)
  2. 可能な場合、compile fileTree(dir: 'lib', include: ['*.jar'])の方法で依存関係を使用しないでください。 MavenやJCenterなどのリポジトリに基づく管理された依存関係は、ランダムなディレクトリにある依存関係よりも一貫して作業しやすいです。これらがオープンソースのアーティファクトリポジトリに公開したくない内部ライブラリである場合、ローカルNexusインスタンスなどをセットアップする価値があります。
  3. 「シンジャー」ではなく「ファットジャー」を作成することを検討してください。これは、すべてのランタイム依存関係が、ビルドするjarにパッケージ化されることを意味します。 Gradleのシャドウプラグイン をお勧めします-これを_build.gradle_に配置し、_gradle clean shadow_を実行すると、_Java -jar_を実行できました手動でクラスパスに何かを追加する必要なく、問題ありません。
14
Olaf