gradle 的 jar 任务制作的 Jar 不够 "fat"
Jar made by gradle's jar task is not "fat" enough
...显然依赖项中没有包含一些必要的东西。
一旦到达对外部库的调用,它就会中断,要么出现 ClassNotFoundException,要么什么也没说。
我从 this skeleton project 开始。
build.gradle
中的相关变化:
application {
mainClassName = 'net.laca.FoKt'
}
(我的主要功能在fo.kt)
dependencies {
//...
compile "com.sparkjava:spark-core:2.9.3"
implementation 'com.google.code.gson:gson:2.8.6'
implementation fileTree('libs') { include '*.jar' }
}
jar {
archiveBaseName = 'csira'
// Uncommend the last two lines to build a "fat" jar with `./gradlew jar`,
// and run it without Gradle's help: `java -jar build/libs/skeleton.jar`
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
版本:Kotlin 1.4.20,Java11,Gradle6.7.1
据说它应该这样工作。就像我以 gradle run
开头一样。
但是当我在 gradle jar
之后用 java -jar build/libs/csira.jar
开始时,它不会。
fo.kt
的相关部分:
package net.laca
import spark.Spark.*
import com.google.gson.GsonBuilder
fun main(args: Array<String>) {
before("/*")
{ req, res ->
res.type("application/json")
println("hívás: ${req.requestMethod()} ${req.pathInfo()} " + req.queryString())
println(GsonBuilder().create().toJson(req.queryMap().toMap())) //line 14
//...
}
在 GsonBuilder 中它中断了:
java.lang.NoClassDefFoundError: com/google/gson/GsonBuilder
at net.laca.FoKt$main.handle(fo.kt:14)
at spark.FilterImpl.handle(FilterImpl.java:73)
at spark.http.matching.BeforeFilters.execute(BeforeFilters.java:48)
at spark.http.matching.MatcherFilter.doFilter(MatcherFilter.java:133)
at ...
...
Caused by: java.lang.ClassNotFoundException: com.google.gson.GsonBuilder
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 19 more
当我 take/comment 输出第 14 行时,它在 /libs
中调用了我自己的 jar:
get("/whatever")
{
println("before")
com.zz.app.MyScalaClass.apply().myFun()
println("after")
}
然后我最后看到的是before
,剩下的就是沉默
发生这种情况是因为您的 jar
任务配置不正确。要了解原因,请查看您的依赖项:
dependencies {
//...
compile "com.sparkjava:spark-core:2.9.3"
implementation 'com.google.code.gson:gson:2.8.6'
implementation fileTree('libs') { include '*.jar' }
}
您正在使用 compile
和 implementation
配置。前者已弃用,不应该顺便使用。
再看jar
任务:
jar {
archiveBaseName = 'csira'
// Uncommend the last two lines to build a "fat" jar with `./gradlew jar`,
// and run it without Gradle's help: `java -jar build/libs/skeleton.jar`
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
from
部分指示 Gradle 仅从 compile
配置中收集所有依赖项,这将完全忽略 implementation
配置。
虽然您可以在任何地方将“编译”更改为“实施”,但构建胖罐的正确方法是实际从 runtimeClasspath
配置中收集。这一个扩展了其他配置,如 compile
和 implementation
,还有 runtimeOnly
,您将来可能会发现它很方便。
在 Gradle user guide 中实际上还有一个如何执行此操作的示例。为了适应您的项目,它应该看起来像:
jar {
archiveBaseName = 'csira'
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
dependsOn configurations.runtimeClasspath
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}
}
额外的 dependsOn
行确保 runtimeClasspath
配置在尝试使用它之前已完全解析。另一个区别是它只收集 jar 文件。
...显然依赖项中没有包含一些必要的东西。
一旦到达对外部库的调用,它就会中断,要么出现 ClassNotFoundException,要么什么也没说。
我从 this skeleton project 开始。
build.gradle
中的相关变化:
application {
mainClassName = 'net.laca.FoKt'
}
(我的主要功能在fo.kt)
dependencies {
//...
compile "com.sparkjava:spark-core:2.9.3"
implementation 'com.google.code.gson:gson:2.8.6'
implementation fileTree('libs') { include '*.jar' }
}
jar {
archiveBaseName = 'csira'
// Uncommend the last two lines to build a "fat" jar with `./gradlew jar`,
// and run it without Gradle's help: `java -jar build/libs/skeleton.jar`
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
版本:Kotlin 1.4.20,Java11,Gradle6.7.1
据说它应该这样工作。就像我以 gradle run
开头一样。
但是当我在 gradle jar
之后用 java -jar build/libs/csira.jar
开始时,它不会。
fo.kt
的相关部分:
package net.laca
import spark.Spark.*
import com.google.gson.GsonBuilder
fun main(args: Array<String>) {
before("/*")
{ req, res ->
res.type("application/json")
println("hívás: ${req.requestMethod()} ${req.pathInfo()} " + req.queryString())
println(GsonBuilder().create().toJson(req.queryMap().toMap())) //line 14
//...
}
在 GsonBuilder 中它中断了:
java.lang.NoClassDefFoundError: com/google/gson/GsonBuilder
at net.laca.FoKt$main.handle(fo.kt:14)
at spark.FilterImpl.handle(FilterImpl.java:73)
at spark.http.matching.BeforeFilters.execute(BeforeFilters.java:48)
at spark.http.matching.MatcherFilter.doFilter(MatcherFilter.java:133)
at ...
...
Caused by: java.lang.ClassNotFoundException: com.google.gson.GsonBuilder
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 19 more
当我 take/comment 输出第 14 行时,它在 /libs
中调用了我自己的 jar:
get("/whatever")
{
println("before")
com.zz.app.MyScalaClass.apply().myFun()
println("after")
}
然后我最后看到的是before
,剩下的就是沉默
发生这种情况是因为您的 jar
任务配置不正确。要了解原因,请查看您的依赖项:
dependencies {
//...
compile "com.sparkjava:spark-core:2.9.3"
implementation 'com.google.code.gson:gson:2.8.6'
implementation fileTree('libs') { include '*.jar' }
}
您正在使用 compile
和 implementation
配置。前者已弃用,不应该顺便使用。
再看jar
任务:
jar {
archiveBaseName = 'csira'
// Uncommend the last two lines to build a "fat" jar with `./gradlew jar`,
// and run it without Gradle's help: `java -jar build/libs/skeleton.jar`
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
from
部分指示 Gradle 仅从 compile
配置中收集所有依赖项,这将完全忽略 implementation
配置。
虽然您可以在任何地方将“编译”更改为“实施”,但构建胖罐的正确方法是实际从 runtimeClasspath
配置中收集。这一个扩展了其他配置,如 compile
和 implementation
,还有 runtimeOnly
,您将来可能会发现它很方便。
在 Gradle user guide 中实际上还有一个如何执行此操作的示例。为了适应您的项目,它应该看起来像:
jar {
archiveBaseName = 'csira'
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
dependsOn configurations.runtimeClasspath
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}
}
额外的 dependsOn
行确保 runtimeClasspath
配置在尝试使用它之前已完全解析。另一个区别是它只收集 jar 文件。