【问题标题】:Jar made by gradle's jar task is not "fat" enoughgradle的jar任务做的jar不够“肥”
【发布时间】:2020-12-02 21:31:06
【问题描述】:

...显然一些必要的东西不包含在依赖项中。
一旦它到达对外部库的调用,它就会中断,要么出现 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、Java 11、Gradle 6.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$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

当我删除/注释第 14 行时,它会在 /libs 中调用我自己的 jar:

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

那么我最后看到的是before,剩下的就是沉默。

【问题讨论】:

    标签: kotlin gradle build.gradle spark-java


    【解决方案1】:

    这是因为您的 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 配置中实际收集。这个扩展了其他配置,例如 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文件。

    【讨论】:

    • 这是一个很好的答案,但我会通过在 jar 任务中添加 duplicatesStrategy 来改进它:duplicatesStrategy = DuplicatesStrategy.WARN
    • 是的,我自己在升级到 Gradle 7 时被那个击中了。
    猜你喜欢
    • 2014-07-15
    • 2016-02-04
    • 2019-12-13
    • 2018-11-15
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    • 2020-06-01
    相关资源
    最近更新 更多