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' }
}

您正在使用 compileimplementation 配置。前者已弃用,不应该顺便使用。

再看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 配置中收集。这一个扩展了其他配置,如 compileimplementation,还有 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 文件。