使用gradle进行测试运行时出现NoSuchMethodError

使用gradle进行测试运行时出现NoSuchMethodError,gradle,Gradle,我正在尝试将一个MAVEN java项目移植到gradle。我遇到的一个问题是无法执行单元测试,因为在运行时(执行期间)没有发生任何方法错误。我正在调用FileUtils.write()方法 我修改了代码,以跟踪加载FileUtils类的类加载器中可用的类路径,并得到以下结果: C:/Sdk/gradle-2-7/lib/commons-io-1.4.jar C:/Users/<me>/.gradle/caches/modules-2/files-2.1/commons-io/com

我正在尝试将一个MAVEN java项目移植到gradle。我遇到的一个问题是无法执行单元测试,因为在运行时(执行期间)没有发生任何方法错误。我正在调用
FileUtils.write()
方法

我修改了代码,以跟踪加载FileUtils类的类加载器中可用的类路径,并得到以下结果:

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
这会将所有gradle依赖项都渗透到我的项目中。尽管我仍然没有找到解决方法:

  • 我无法删除它,因为项目无法编译
  • 我不能排除某些内容,因为gradle不支持
    gradleApi()
    (请参阅)
  • 我不能只添加我真正需要的gradle jar作为依赖项—我看不到任何在编译依赖项中显式引用它们的方法,我看不到任何包含这些工件的公共存储库。注意:对于MAVEN build,我已经手动将它们上传到本地MAVEN repo

    • TL;DR

      不要使用commons io:2.1,或者使用Java8、Groovy,或者使用一个助手来代替commons io

      解释

      根本原因是
      gradleApi()
      包含commons io:1.4,当测试执行时,类路径上有两个commons io jar,而1.4版本恰好更早,因此您会得到
      NoSuchMethodError

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


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

      好的,我最初关于“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文件直接引用它们的实现


      问题是您试图使用FileUtils.write,但较旧版本(1.4)没有该方法(类“冲突”)?这是正在发生的问题,但根问题略有不同-我在插件中引用的库使用较新版本的commons io,而gradle提供的运行时提供较旧版本。所以gradle应该在测试时提供类加载器的分离,让“自己的代码”和“单元测试下的插件代码”与不同版本的第三方LIB共存。gradle在这里做的是正确的,并保护您不进行错误的测试。当您加载插件时,它将运行到您的测试所看到的同一个jar未匹配项中。我会尝试使用Java8或Groovy来避开这个库的需要,我不同意“Gradle在这里做的事情是正确的”。在“测试时”和“运行时”,目标插件中类的内部引用(在本例中为commons io)应该与gradle的内部引用隔离。MAVEN通过专用类加载器对插件代码进行“测试”和“运行时”执行。但当插件执行时,它将首先在类路径上加载所有内部JAR。因此,它的行为就像你的插件一样。“不要使用commons io:2.1”表示不要使用任何使用“commons io:2.1”的库,这意味着-重写整个库“或者使用Java8、Groovy,…”这当然是不可接受的。另一种方法是创建一个主类,然后在javaexec中加载一个完全独立的类加载器,您可以在其中添加任何您想要的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")
      }