如何在Gradle中导出可执行的jar包,并且该jar包可以运行,因为它包含了引用库。

43

如何在Gradle中导出可执行的Jar文件,且该Jar文件包含了所需的引用库。

build.gradle

apply plugin: 'java'

manifest.mainAttributes("Main-Class" : "com.botwave.analysis.LogAnalyzer")

repositories {
    mavenCentral()
}

dependencies {
    compile (
        'commons-codec:commons-codec:1.6',
        'commons-logging:commons-logging:1.1.1',
        'org.apache.httpcomponents:httpclient:4.2.1',
        'org.apache.httpcomponents:httpclient:4.2.1',
        'org.apache.httpcomponents:httpcore:4.2.1',
        'org.apache.httpcomponents:httpmime:4.2.1',
        'ch.qos.logback:logback-classic:1.0.6',
        'ch.qos.logback:logback-core:1.0.6',
        'org.slf4j:slf4j-api:1.6.0',
        'junit:junit:4.+'
    )
}

运行 gradle build 后,它会创建 build 文件夹,然后我在 build/libs/XXX.jar 中运行 jar 包:

java -jar build/libs/XXX.jar

执行结果如下:

Exception in thread "main" java.lang.NoClassDefFoundError: ch/qos/logback/core/joran/spi/JoranException

我该如何使用引用库运行它?

5个回答

18

太酷了,我不知道他们已经添加了这个功能。我总是使用“fat jar”方法,并手动添加主类的清单条目。 - Alan Krueger
8
我不想得到一个zip或tar文件,只需要将依赖库的JAR文件放在一个文件夹中。 - jychan
11
Gradle应用程序插件不会创建可执行jar文件。 - jeremyjjbrown
如果您在build.gradle文件中遇到任何错误,请务必使用“>gradle run”测试应用程序。在运行Gradle Application插件的任何“分发任务”之前,先运行“>gradle distJar”。 - fishjd
1
你无法通过Gradle应用程序插件来实现它。 - Yura Shinkarev
显示剩余2条评论

17
希望这能够帮助到某些人(因为我不幸地花费了很多时间来寻找解决方案)。这是解决为我创建可执行JAR文件的方法。在主方法中嵌入Jetty,具体而言是Jetty 9,并使用Gradle 2.1。
将以下代码包含到您的build.gradle文件中(如果子项目是需要构建jar文件的“主”项目,则将其添加到子项目,该子项目应该以project(':') {开始,在dependencies之后的某个地方插入代码)。
此外,您需要添加java插件才能使此功能生效:apply plugin: 'java'。
我的jar任务如下所示:
apply plugin: 'java'

jar {

    archiveName = "yourjar.jar"

    from {

        configurations.runtime.collect {
            it.isDirectory() ? it : zipTree(it)
        }

        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }

    manifest {
        attributes 'Main-Class': 'your.package.name.Mainclassname'
    }

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

然后,您可以通过命令行执行您的yourjar.jar文件:

java -jar yourjar.jar

为使其正常工作,必须排除 META-INF/.RSA、META-INF/.SF 和 META-INF/*.DSA。否则会抛出 SecurityException。

问题似乎出现在嵌入式 Jetty 上,因为 Jetty 移动到 Eclipse 平台并开始对其 JAR 进行签名,据我所知,当其他未签名的 JAR 要加载已签名的 JAR 时就会出现问题。如果我理解有误,请随时指教。

该项目依赖的 JAR 包在依赖项中定义如下:

dependencies {

    // add the subprojects / modules that this depends on
    compile project(':subproject-1')
    compile project(':subproject-2')

    compile group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.2.6.v20141205'
    compile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.2.6.v20141205'
    compile group: 'org.eclipse.jetty', name: 'jetty-http', version: '9.2.6.v20141205'

}

编辑:在此之前,只需使用

而不是
configurations.runtime.collect{...}

i had

configurations.runtime.asFileTree.files.collect{...}

这在一个大型项目的清洁构建中会导致奇怪的行为。当第一次执行gradle clean build(手动清理构建目录后)后运行jar文件时(在我们有许多子项目的项目中),它会抛出NoClassDefFoundException异常,但在第二次执行gradle clean build(不手动清空构建目录)后运行jar文件时,由于某种原因它已拥有所有依赖项。如果忽略asFileTree.files,则此问题不会发生。
另外我应该指出,所有编译依赖项都包含在运行时中,但并非所有运行时都包含在编译中。因此,如果只使用编译选项,可能会出现问题。
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }

请记住,如果抛出NoClassDefFoundException异常,表示某个类在运行时未找到,因此您还应包括以下内容:
        configurations.runtime.collect {
            it.isDirectory() ? it : zipTree(it)
        }

我还应该注意,在我的代码中,依赖项{...}部分位于jar{...}部分之前,如果相关的话。 - flamingo
-1 OP 明确表示:他/她想要创建一个包含所有“参考库”的“fat jar”。当然,你不是唯一一个回答错误问题的人。请看我的答案。 - mike rodent
我看不出OP在哪里使用了“fat jar”这个词。OP明确谈到了可执行jar。我知道可执行jar基本上被称为fat jar,但是这是否立即意味着您必须使用fat jar插件?此外,我明确说明了我正在使用哪些版本,因此它可能已经过时,但仍可以与我提到的gradle等版本一起使用。 - flamingo
原帖作者不知道“fat jar”这个术语,我同意!但是这个想法还能怎么解释:“这个jar包可以运行,因为它包含了引用库。”?当然,“引用库”这个术语指的是构成依赖关系的jar包……而原帖作者想知道为什么会收到错误消息……“fat jar”确实指的是可执行的jar包,但具体来说是包含所有依赖jar包的可执行jar包。 - mike rodent

12

快速回答

  1. 将以下内容添加到你的 build.gradle 文件中:

    apply plugin: 'application'
    
    mainClassName = 'org.example.app.MainClass'
    
    jar {
        manifest {
            attributes 'Main-Class': mainClassName,
                       'Class-Path': configurations.runtime.files.collect {"$it.name"}.join(' ')
        }
    }
    
  2. 从项目目录中运行gradle installDist
  3. 运行java -jar build/install/<appname>/lib/<appname>.jar

我建议将应用程序版本添加到你的build.gradle文件中,但这不是必需的。如果你这样做了,构建的jar文件名称将为<appname>-<version>.jar

注意:我正在使用gradle 2.5


详情

为了创建一个自包含的可执行jar文件,你只需运行:

java -jar appname.jar

你需要:

  1. 你的jar包内要包含一个指向应用程序主类的MANIFEST文件
  2. 所有的依赖项(来自应用程序外的jar包中的类)都需要被包含或以某种方式被访问
  3. 你的MANIFEST文件要包含正确的类路径

正如其他答案所指出的,你可以使用一些第三方插件来实现这一点,比如 shadow 或者 one-jar

我尝试了 shadow,但是不喜欢这样的事实:我的所有依赖项和它们的资源都与我的应用程序代码一起被倾倒到构建的 jar 包中。我也更喜欢最小化使用外部插件。

另一种选择是使用 gradle 应用程序插件,就像 @erdi 上面回答的那样。运行 gradle build 将为您构建一个 jar 文件,并将其与所有依赖项一起打包成 zip/tar 文件。你也可以直接运行 gradle installDist 跳过压缩。

然而,正如 @jeremyjjbrown 在评论中所写的那样,该插件并不直接创建可执行的 jar 文件。它创建了一个 jar 文件和一个脚本,该脚本构建类路径并执行一个命令来运行应用程序的主类。你将不能运行 java -jar appname.jar

为了兼顾两个世界,按照上述步骤创建包含所有依赖项的 jar 包,并向你的 MANIFEST 添加正确的值。


谢谢,伙计,这个可以工作,但是关于依赖项,如果我想创建一个包含所有依赖项的jar文件呢? - max
@max 抱歉,我没有完全理解你的问题。上述操作将为您的应用程序创建一个单独的 jar 文件,其中包含所有依赖项作为单独的 jar 文件。MANIFEST 类路径将指向您所依赖的所有 jar 文件。 - elanh
1
是的,那是正确的,但如果我想要将所有相关依赖和应用程序都打包到单个 jar 包中怎么办? - max
@max 我认为你可以使用我在答案中提供链接的 shadowone-jar 插件。 - elanh

7
所有这些答案都是错误的或已过时。
OP正在寻找所谓的“fat jar”。这是一个可执行的jar文件,其中包含所有依赖项,因此运行它不需要外部依赖项(当然,除了JRE!)。
撰写本文时的答案是Gradle Shadow Jar插件,在Shadow Plugin用户指南和示例中得到了很清楚的解释。
我有点苦恼。但这个方法可以:
在您的build.gradle文件中的某个地方(我将它们放在顶部附近)放置所有这些行:
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.4' 
    }
}
apply plugin: 'com.github.johnrengelman.shadow'
shadowJar {
    baseName = 'shadow'
    classifier = null
    version = null
}
jar {
    manifest {
        attributes 'Class-Path': '/libs/a.jar'
        attributes 'Main-Class': 'core.MyClassContainingMainMethod'
    }
}

请不要担心构建文件中其他任何“repositories”、“dependency”或“plugin”行,确保在此“buildscript”块内保留这些行(我不知道为什么需要这样做)。

另外,Shadow Plugin用户指南和示例写得很好,但没有告诉您要包含该行。

attributes 'Main-Class': 'core.MyClassContainingMainMethod'

我已经将它放在上面。也许是因为作者假设你比我更聪明,而且你可能确实如此。我不知道为什么我们被告知要像那样放置一个奇怪的“Class-Path”属性,但如果它没有出问题,就不要修理它。

然后当你继续进行时

> gradle shadowjar

希望Gradle能够构建一个“fat”可执行jar包,位于/build/libs目录下(默认名称为“shadow.jar”),您可以通过以下方式运行它:
> java -jar shadow.jar

@elanh的答案在使用gradle时非常好用,无需使用shadowjar。 - marcoslhc
@marcoslhc,我认为你没有理解问题:OP想要一个包含所有“compile”依赖项jar的jar。因此,您可以将此jar放在任何地方并运行java -jar myFatJar.jar...它将运行。Elanh在他的问题的评论中也说了同样的话。 - mike rodent
救了我的一天!这个解决方案自2018年2月以来一直在为我的项目工作。 - czxttkl

1
我是一个有用的助手,可以翻译文本。

我查看了很多链接来解决问题,最终采取以下步骤使其正常工作。我正在使用Gradle 2.9。

在你的build.gradle文件中进行以下更改:

1. 提及插件:

apply plugin: 'eu.appsatori.fatjar'

提供Buildscript:
buildscript {
  repositories {
    jcenter()
   }

dependencies {
  classpath "eu.appsatori:gradle-fatjar-plugin:0.3"
  }
}

3. Provide the Main Class:

fatJar {
  classifier 'fat'
  manifest {
     attributes 'Main-Class': 'my.project.core.MyMainClass'
  }
  exclude 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.SF'
}

创建fatjar:

4. 创建fatjar:

./gradlew clean fatjar

5. 从 /build/libs/ 运行 fatjar 文件:

java -jar MyFatJar.jar

尝试过这个...出现了“eu.appsatori.fatjar未找到”的错误。在这里https://plugins.gradle.org/plugin/eu.appsatori.fatjar,它指向了https://github.com/musketyr/gradle-fatjar-plugin,该页面上写着“不再进行积极开发,请使用“Gradle Shadow Plugin”。但至少你回答了这个问题! - mike rodent

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