【问题标题】:NoSuchMethodError under test run with gradle使用 gradle 测试运行的 NoSuchMethodError
【发布时间】:2016-01-19 13:21:51
【问题描述】:

我正在尝试将一个 MAVEN java 项目移植到 gradle。我遇到的问题之一是由于在运行时(执行期间)发生 NoSuchMethodError 而无法执行单元测试。我正在调用FileUtils.write() 方法。

我修改了我的代码以跟踪加载 FileUtils 类的 ClassLoader 中可用的类路径,我得到了以下信息:

C:/Sdk/gradle-2-7/lib/commons-io-1.4.jar
C:/Users/<me>/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/2.4/b1b6ea3b7e4aa4f492509a4952029cd8e48019ad/commons-io-2.4.jar

我看到在测试运行期间类路径中有 2 个版本的 commons-io,而带有 gradle 的版本是第一个,因此具有更高的优先级。

根本原因是什么?如何解决这个问题?

实际上,除了在我的 gradle 项目的依赖项中明确声明之外,我希望类路径中没有可用的 JAR。


更新:似乎我对根本原因有所了解 - 正在测试的项目是“gradle plugin”,要在 gradle 中编译它,我必须在 build.gradle 中指定以下内容:

dependencies {
    compile gradleApi()
}

这渗透到我的项目中捕获所有 gradle 依赖项。虽然我仍然看不到修复它的方法:

  • 我无法删除它,因为项目无法编译
  • 我不能排除某些东西,因为 gradle 不支持 gradleApi()(参见 http://gradle.1045684.n5.nabble.com/exclude-some-dependencies-from-gradleApi-dependency-td5712103.html)。
  • 我不能只添加那些我真正需要的 gradle jar 作为依赖项——我没有看到任何在编译依赖项中明确引用它们的方法,我没有看到任何包含这些工件的公共存储库。注意:对于 MAVEN 构建,我已手动将它们上传到本地 MAVEN 存储库。

【问题讨论】:

  • 是您尝试使用 FileUtils.write 但旧版本 (1.4) 没有该方法(类“碰撞”)的问题吗?
  • 这是正在发生的问题,但根本问题略有不同 - 我在插件中引用的库使用较新版本的 commons-io,而 gradle 提供的运行时提供较旧的一。所以...... gradle 应该以某种方式在测试时提供 ClassLoaders 的分离,以让“自己的代码”和“单元测试中的插件代码”与不同版本的 3rd 方库共存。
  • Gradle 在这里做的是正确的事情,并保护您免受不正确的测试。当你的插件被加载时,它会运行到你的测试看到的同一个 jar 不匹配中。我会尝试使用 Java8 或 Groovy 来解决需要这个库的问题。
  • 我不同意“Gradle 在这里做正确的事情”。在“测试时间”和“运行时间”,目标插件中类的内部引用(在这种情况下是 commons-io)应该与 gradle 的内部引用隔离。 MAVEN 通过专用的 ClassLoaders 执行插件代码的“测试”和“运行时”。
  • 但是当插件执行时,它将首先在类路径上加载所有内部 jar。所以它的行为就像你的插件一样。

标签: gradle


【解决方案1】:

TL;DR

不要使用 commons-io:2.1,要么使用 Java8、Groovy,要么使用 helper 代替 commons-io。

说明

根本原因是gradleApi() 包含commons-io:1.4,当测试执行时,您最终在类路径上有两个commons-io jar,而1.4 版本恰好更早,所以这就是您获得@ 的原因987654323@.

这是因为 Gradle 不像 Maven 那样将每个插件隔离在单独的类加载器中。鉴于 Gradle 的工作方式,你不能。这是因为在 Gradle 中一个好的设计策略是让多个插件链接在一起做一件事。您有一个插件可以将事物配置为通用的,并且对您将如何使用它没有意见。然后你有一个非常自以为是的插件,它用很多假设来配置一个项目。当用户与您的意见不同时,这很有帮助,他们只需应用基本插件并应用他们自己的意见。

举个更具体的例子,你有一个 jar,其中包含其他插件使用的 extension。这可能类似于“failBuildOnQualityError”。然后每个单独的插件(使用不同的坐标)会尝试使用该扩展来查看当 checkstyle、findbugs、jacoco 等发现问题时它们是否应该使构建失败。

【讨论】:

  • "Don't use commons-io:2.1" 表示不要使用任何使用 "commons-io:2.1" 的库,这意味着 - 使用 Java8、Groovy、 ......”这当然是不可接受的。
  • 另一种方法是创建一个主类,然后在 javaexec 中加载一个完全独立的类加载器,您可以在其中添加您想要的任何 jar。
【解决方案2】:

好的,我对“Gradle 没有将插件的类路径与自己的类路径隔离”的初步诊断是错误的。根本原因其实是……

dependencies {
    compile gradleApi()
}

... 摄取到许多依赖项(包括 commons-io:1.4)。正确的解决方案(至少对我来说是可行的)是只明确包含所需的 jar:

versions = [
    gradle: "2.8",
    groovy: "2.4.4",
]

dependencies {
    compile "org.codehaus.groovy:groovy-all:${versions.groovy}"
    compile "org.gradle:gradle-base-services:${versions.gradle}"
    compile "org.gradle:gradle-base-services-groovy:${versions.gradle}"
    compile "org.gradle:gradle-core:${versions.gradle}"
    compile files("lib/gradle-platform-jvm-${versions.gradle}.jar")
}

注意事项:

  • gradle 的核心依赖项应该通过公共工件 ID 引用(而不是通过直接 JAR 文件引用)。这样,运行时的 gradle 使用关联的 POM 来构建传递的运行时依赖项。否则在执行单元测试期间你会得到 NoClassDefFoundError 因为其他 gradle 的内部不在执行类路径中。

  • 看起来并非所有 gradle 的工件都在公共存储库 (jcenter) 中可用。如果在您的插件中您要构建对其他“本机”任务(如“jar”)的依赖项 - 它们的实现必须通过 JAR 文件直接引用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-23
    • 2015-03-09
    • 1970-01-01
    • 2014-07-11
    • 2015-12-28
    • 2014-01-09
    • 2018-08-19
    • 1970-01-01
    相关资源
    最近更新 更多