首页 > 解决方案 > Jar made by gradle's jar task is not "fat" enough

问题描述

...obviously some necessary things are not included from the dependencies.
Once it reaches a call to an external library, it breaks, either with ClassNotFoundException, or without a word.

I started with this skeleton project.
Relevant changes in build.gradle:

application {
    mainClassName = 'net.laca.FoKt'
}

(my main function is in 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) } }
}

versions: Kotlin 1.4.20, Java 11, Gradle 6.7.1

Allegedly it should work this way. As it does if I start it with gradle run. But when I start it with java -jar build/libs/csira.jar after gradle jar, it doesn't.

Relevant parts of 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
    //...
  }

At GsonBuilder it breaks:

java.lang.NoClassDefFoundError: com/google/gson/GsonBuilder
    at net.laca.FoKt$main$1.handle(fo.kt:14)
    at spark.FilterImpl$1.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

And when I take/comment out the 14th line, and it reaches a call to my own jar in /libs:

  get("/whatever")
  {
    println("before")
    com.zz.app.MyScalaClass.apply().myFun()
    println("after")
  }

then the last thing I see is before, the rest is silence.

标签: kotlingradlebuild.gradlespark-java

解决方案


发生这种情况是因为您的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配置。

虽然您可以在任何地方将“编译”更改为“实现”,但构建胖 jar 的正确方法是从runtimeClasspath配置中实际收集。这个扩展了其他配置,例如compileand implementation,但runtimeOnly您将来可能会发现它很方便。

Gradle 用户指南中实际上也有一个如何执行此操作的示例。为了适应您的项目,它应该如下所示:

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 文件。


推荐阅读