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

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

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

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

【问题讨论】:

    标签: java gradle jar build.gradle executable-jar


    【解决方案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。我会坚持使用一个罐子:它更简单,更易于使用。凉爽的!谢谢
    • 不幸的是,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://stackoverflow.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
    

    【讨论】:

    • 当我执行此构建时,我得到 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 文件中进行以下更改:

          1. 提及插件:

            apply plugin: 'eu.appsatori.fatjar'
            
          2. 提供构建脚本:

            buildscript {
            repositories {
                jcenter()
            }
            
            dependencies {
                classpath "eu.appsatori:gradle-fatjar-plugin:0.3"
            }
            }
            
          3. 提供主类:

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

            ./gradlew clean fatjar
            
          5. 从 /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】:
            1. 为您的清单配置主类

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

            jar {
                manifest {
                    attributes(
                           'Main-Class': 'pokerhandscorer.PokerHandScorer'
                    )
                }
            }
            
            • 其中“pokerhandscorer”是包名, PokerHandScorer 是主要的类名

            这会在您的 \build\libs{jarFilename}.jar 中创建一个 jar 文件

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

            java -jar /{path}/{jarFileName.jar}

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-02-06
              • 2021-01-26
              • 1970-01-01
              • 2018-07-24
              • 2014-04-01
              • 1970-01-01
              • 2012-03-06
              • 1970-01-01
              相关资源
              最近更新 更多