使用 Gradle 创建可运行的 JAR

Posted

技术标签:

【中文标题】使用 Gradle 创建可运行的 JAR【英文标题】:Creating runnable JAR with Gradle 【发布时间】:2014-03-10 09:01:55 【问题描述】:

到目前为止,我通过 Eclipse“导出...”功能创建了可运行的 JAR 文件,但现在我切换到 IntelliJ IDEA 和 Gradle 以实现构建自动化。

这里的一些文章建议使用“应用程序”插件,但这并不能完全达到我预期的结果(只是一个 JAR,没有启动脚本或类似的东西)。

我怎样才能获得与 Eclipse 的“导出...”对话框相同的结果?

【问题讨论】:

【参考方案1】:

可执行的 jar 文件只是一个 jar 文件,其清单中包含 Main-Class 条目。所以你只需要配置jar task 以便在其清单中添加此条目:

jar 
    manifest 
        attributes 'Main-Class': 'com.foo.bar.MainClass'
    

您可能还需要在清单中添加类路径条目,但方法相同。

见http://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html

【讨论】:

这似乎是我想要的;但是:我已经在 build.gradle 中声明了依赖项,我真的必须手动添加类路径还是可以重新使用我的依赖项声明? 您应该能够遍历“运行时”配置中的库并将它们连接起来以创建 Class-Path 属性值。 'inseid the "runtime" configuration' 是什么意思?对不起,愚蠢的问题,我对 Gradle 很陌生...... gradle java 插件定义了 4 个“配置”对应 4 个不同的类路径:compile(用于编译 Java 文件)、testCompile(用于编译测试 Java 源文件)、runtime(用于执行应用程序)和 testRuntime(用于执行测试)。见gradle.org/docs/current/userguide/… 添加清单属性也作为添加chmod +x 属性到文件中完全不足以使JAR 可执行,并且与Eclipse 命令Export to 完全不同。【参考方案2】:

JB Nizet 和 Jorge_B 的答案都是正确的。

在最简单的形式中,使用 Gradle 创建可执行 JAR 只需将适当的条目添加到 manifest。但是,需要在类路径中包含依赖项更为常见,这使得这种方法在实践中很棘手。

application plugin 提供了另一种方法;它没有创建可执行的 JAR,而是提供:

run 任务,便于直接从构建中轻松运行应用程序 installDist 任务生成一个目录结构,包括已构建的 JAR、它所依赖的所有 JAR,以及一个启动脚本,该脚本将所有这些组合到一个您可以运行的程序中 distZipdistTar 任务创建包含完整应用程序分发(启动脚本和 JAR)的档案

第三种方法是创建一个所谓的“胖 JAR”,它是一个可执行的 JAR,它不仅包含组件的代码,还包含它的所有依赖项。有几个不同的插件使用这种方法。我已经包含了一些我知道的链接;我敢肯定还有更多。

shadow one-jar spring-boot capsule

【讨论】:

刚试过shadow和one-jar。我会坚持使用一个罐子:它更简单,更易于使用。凉爽的!谢谢 不幸的是,one-jar 不适用于最新版本的 Gradle。例如,参见github.com/rholder/gradle-one-jar/issues/34 Here 是一种通过修改jar任务来构建one-jar的单线解决方案。我发现这是最方便的。请注意,您需要添加 configurations.runtime 以在单个 jar 中捆绑运行时依赖项。 我发现“应用程序插件”非常适合和灵活地满足我的需要。它还允许将所有内容打包到 zip 并在该 zip 中包含/排除额外文件。此外,还可以使用自定义任务添加另一个带有额外入口点的启动器脚本。 我将如何执行生成的.tar / .zip 文件?【参考方案3】:

正如其他人所指出的,为了使 jar 文件可执行,必须在清单文件的 Main-Class 属性中设置应用程序的入口点。如果依赖类文件没有搭配,则需要在manifest文件的Class-Path入口设置。

我已经尝试了各种插件组合,以及创建可执行 jar 的简单任务以及以某种方式包含依赖项的简单任务。所有插件似乎都缺少一种或另一种方式,但最终我得到了我想要的。没有神秘的脚本,没有污染构建目录的一百万个不同的迷你文件,一个非常干净的构建脚本文件,最重要的是:没有一百万个外部第三方类文件合并到我的 jar 存档中。

以下是来自here 的复制粘贴,为您提供方便..

[How-to] 在子目录 /lib 中创建一个包含依赖 jar 的分发 zip 文件,并将所有依赖项添加到清单文件中的 Class-Path 条目:

apply plugin: 'java'
apply plugin: 'java-library-distribution'

repositories 
    mavenCentral()


dependencies 
    compile 'org.apache.commons:commons-lang3:3.3.2'


// Task "distZip" added by plugin "java-library-distribution":
distZip.shouldRunAfter(build)

jar 
    // Keep jar clean:
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'

    manifest 
        attributes 'Main-Class': 'com.somepackage.MainClass',
                   'Class-Path': configurations.runtime.files.collect  "lib/$it.name" .join(' ')
    
    // How-to add class path:
    //     https://***.com/questions/22659463/add-classpath-in-manifest-using-gradle
    //     https://gist.github.com/simon04/6865179

作为要点主持here。

结果可以在build/distributions找到,解压后的内容如下:

lib/commons-lang3-3.3.2.jar MyJarFile.jar

MyJarFile.jar#META-INF/MANIFEST.mf的内容:

清单版本:1.0 主类:com.somepackage.MainClass 类路径:lib/commons-lang3-3.3.2.jar

【讨论】:

当前应用程序 jar 也将在发行版的“lib”目录中。您必须将其移动到***目录或更改此行: 'Class-Path': configurations.runtime.files.collect "lib/$it.name" .join(' ') 到此: '类路径':configurations.runtime.files.collect "$it.name" .join(' ') @MarcNuri 你确定吗?我尝试在我的应用程序中使用这种方法,当前应用程序 jar 不是在生成的 zip/tar 文件的 lib 目录中,而是在 lib 的父目录中,如这个答案表明。这个解决方案似乎对我很有效。 @thejonwithnoh 对不起,你是对的。我没有看到建议使用“java-library-distribution”插件的解决方案。在我的情况下,我只是使用“应用程序”插件来完成相同的工作,主要区别在于所有 jar 文件(包括应用程序 jar)都位于“lib”目录中。因此,将 "lib/$it.name" 更改为 "$it.name" 即可。【参考方案4】:

对我来说最省力的解决方案是使用gradle-shadow-plugin

除了应用插件之外,还需要做的是:

配置 jar 任务以将 Main 类放入清单中

jar 
  manifest 
   attributes 'Main-Class': 'com.my.app.Main'
  

运行 gradle 任务

./gradlew shadowJar

build/libs/ 获取 app-version-all.jar

最后通过以下方式执行:

java -jar app-version-all.jar

【讨论】:

这是full build.gradle example。 当我执行此构建时,我得到 BUILD SUCCESSFUL 但是当我尝试使用 java -jar build/libs/core-all-1.0.jar 运行 jar 文件时,我收到以下错误:错误:找不到或加载主类scanners.exchange.Main 引起:java.lang.ClassNotFoundException:scanners.exchange.Main 你知道我该如何解决吗? @LukaLopusina 您指定的类不在您的 JAR 文件中。如果你使用 kotlin,你需要说'com.my.app.MainKt'。如果没有更多信息,我无法进一步帮助您。 当前插件 Shadow v5.+ 仅兼容 Gradle 5.0+ 和 Java 7+。【参考方案5】:

可以使用SpringBoot插件:

plugins 
  id "org.springframework.boot" version "2.2.2.RELEASE"

创建罐子

gradle assemble

然后运行它

java -jar build/libs/*.jar

注意:您的项目不需要是 SpringBoot 项目即可使用此插件。

【讨论】:

【参考方案6】:

您是否尝试过“installApp”任务?它不会用一组启动脚本创建一个完整的目录吗?

http://www.gradle.org/docs/current/userguide/application_plugin.html

【讨论】:

据我了解,installApp 不会创建 META-INF/MANIFEST.MF 文件。我做错了吗? 我在Application Plugin的任务列表中没有看到installApp任务。你的意思是installDist 吗? 是的,installApp 在 Gradle 3.0 中已重命名为 installDist。这是release note。【参考方案7】:

感谢 Konstantin,它的作用就像一个没有细微差别的魅力。出于某种原因,将主类指定为 jar 清单的一部分并不太奏效,而是需要 mainClassName 属性。这是来自 build.gradle 的一个 sn-p,其中包括使其工作的所有内容:

plugins 
  id 'java' 
  id 'com.github.johnrengelman.shadow' version '1.2.2'

...
...
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
...
...
mainClassName = 'com.acme.myapp.MyClassMain'
...
...
...
shadowJar 
    baseName = 'myapp'

运行 gradle shadowJar 后,您会在构建文件夹中获得 myapp-version-all.jar,它可以作为 java -jar myapp-version-all.jar 运行。

【讨论】:

【参考方案8】:

您可以在模块设置(或项目结构)中定义一个 jar 工件。

右键单击模块 > 打开模块设置 > 工件 > + > JAR > 来自具有依赖项的模块。 设置主类。

制作一个 jar 就像从 Build 菜单中单击“Build artifact...”一样简单。作为奖励,您可以将所有依赖项打包到一个 jar 中。

在 IntelliJ IDEA 14 Ultimate 上测试。

【讨论】:

【参考方案9】:

我检查了很多解决方案的链接,最后完成了下面提到的步骤以使其正常工作。我正在使用 Gradle 2.9。

在您的 build、gradle 文件中进行以下更改:

    提及插件:

    apply plugin: 'eu.appsatori.fatjar'
    

    提供构建脚本:

    buildscript 
    repositories 
        jcenter()
    
    
    dependencies 
        classpath "eu.appsatori:gradle-fatjar-plugin:0.3"
    
    
    

    提供主类:

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

    创建fatjar:

    ./gradlew clean fatjar
    

    从 /build/libs/ 运行 fatjar:

    java -jar MyFatJar.jar
    

【讨论】:

从 2019 年开始:此建议在此处不起作用。 fatjar 中不包含任何依赖项【参考方案10】:

这是我尝试使用 Gradle 6.7 的解决方案

可运行的胖 Jar(所有依赖库都复制到 jar 中)

task fatJar(type: Jar) 
   
    manifest 
        attributes 'Main-Class': 'com.example.gradle.App'
    
    from 
        configurations.compile.collect  it.isDirectory() ? it : zipTree(it) 
     with jar
    

将所有依赖项复制到目录并将类路径添加到清单中的可运行 jar

    def dependsDir = "$buildDir/libs/dependencies/"
    task copyDependencies(type: Copy) 
        from configurations.compile
        into "$dependsDir"
    
    task createJar(dependsOn: copyDependencies, type: Jar) 
      
        manifest 
            attributes('Main-Class': 'com.example.gradle.App',
                    'Class-Path': configurations.compile.collect  'dependencies/' + it.getName() .join(' ')
            )
        
        with jar
    

如何使用?

将上述任务添加到 build.gradle 执行gradle fatJar //创建fatJar Execute gradle createJar // 创建 jar 并复制依赖项。

更多详情:https://jafarmlp.medium.com/a-simple-java-project-with-gradle-2c323ae0e43d

【讨论】:

【参考方案11】:

这适用于 Kotlin DSL (build.gradle.kts)。

方法一(不需要application或其他插件)

tasks.jar 
    manifest.attributes["Main-Class"] = "com.example.MyMainClass"
    // OR another notation
    // manifest 
    //     attributes["Main-Class"] = "com.example.MyMainClass"
    // 

如果您使用任何外部库,请使用以下代码。将库 JAR 复制到放置结果 JAR 的 libs 子目录中。确保您的库 JAR 文件的文件名中不包含空格。

tasks.jar 
    manifest.attributes["Main-Class"] = "com.example.MyMainClass"
    manifest.attributes["Class-Path"] = configurations
        .runtimeClasspath
        .get()
        .joinToString(separator = " ")  file ->
            "libs/$file.name"
        

请注意,Java 要求我们为 Class-Path 属性使用相对 URL。因此,我们不能使用 Gradle 依赖项的绝对路径(这也容易被更改并且在其他系统上不可用)。如果你想使用绝对路径,也许this workaround 可以。

使用以下命令创建 JAR:

./gradlew jar

默认会在build/libs/目录中创建结果JAR。

方法 2:在结果 JAR 文件中嵌入库(如果有)

tasks.jar 
    manifest.attributes["Main-Class"] = "com.example.MyMainClass"
    val dependencies = configurations
        .runtimeClasspath
        .get()
        .map(::zipTree) // OR .map  zipTree(it) 
    from(dependencies)
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

创建 JAR 与之前的方法完全相同。

方法三:使用Shadow plugin。

plugins 
    id("com.github.johnrengelman.shadow") version "6.0.0"

// Shadow task depends on Jar task, so these will be reflected for Shadow as well
tasks.jar 
    manifest.attributes["Main-Class"] = "org.example.MainKt"

使用以下命令创建 JAR:

./gradlew shadowJar

有关配置插件的更多信息,请参阅Shadow documentations。

运行创建的 JAR

java -jar my-artifact.jar

以上解决方案经过以下测试:

Java 17 Gradle 7.1(将 Kotlin 1.4.31 用于 .kts 构建脚本)

见官方Gradle documentation for creating uber (fat) JARs。

有关清单的更多信息,请参阅Oracle Java Documentation: Working with Manifest files。

请注意,您的资源文件将自动包含在 JAR 文件中(假设它们放置在 /src/main/resources/ 目录或构建文件中设置为资源根目录的任何自定义目录中) .要访问应用程序中的资源文件,请使用以下代码(注意名称开头的 /):

科特林
val vegetables = MyClass::class.java.getResource("/vegetables.txt").readText()
// Alternative ways:
// val vegetables = object.javaClass.getResource("/vegetables.txt").readText()
// val vegetables = MyClass::class.java.getResourceAsStream("/vegetables.txt").reader().readText()
// val vegetables = object.javaClass.getResourceAsStream("/vegetables.txt").reader().readText()
Java
var stream = MyClass.class.getResource("/vegetables.txt").openStream();
// OR var stream = MyClass.class.getResourceAsStream("/vegetables.txt");
var reader = new BufferedReader(new InputStreamReader(stream));
var vegetables = reader.lines().collect(Collectors.joining("\n"));

【讨论】:

另外,请参阅this post 以创建新任务,而不是修改现有的 Jar 任务。【参考方案12】:
    为您的清单配置主类

如果您使用的是 gradle 项目,只需将以下内容添加到您的 build.gradle 中

jar 
    manifest 
        attributes(
               'Main-Class': 'pokerhandscorer.PokerHandScorer'
        )
    

其中“pokerhandscorer”是包名, PokerHandScorer 是主要的类名

这会在您的 \build\libsjarFilename.jar 中创建一个 jar 文件

    使用 java -jar /path/jarFileName.jar 运行 jar 文件

java -jar /path/jarFileName.jar

【讨论】:

以上是关于使用 Gradle 创建可运行的 JAR的主要内容,如果未能解决你的问题,请参考以下文章

Gradle构建SpringBoot并打包可运行的jar配置

如何使用 Gradle 创建具有实现依赖项的可执行胖 jar

使用 Gradle 设置 AWS 开发环境

运行从基于 gradle 的项目构建的可执行 jar 文件

系列教程1Gradle入门系列二:第一个Java项目

从 Gradle 创建可执行 jar 文件并在 maven 中导入 jar