使用 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,以及一个启动脚本,该脚本将所有这些组合到一个您可以运行的程序中
distZip
和 distTar
任务创建包含完整应用程序分发(启动脚本和 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
【讨论】:
这是fullbuild.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配置