找不到适合抓取的类加载器

时间:2011-01-06 02:26:40

标签: groovy intellij-idea classloader junit4

我在课程开头有这个:

@Grab(group = 'org.ccil.cowan.tagsoup', module = 'tagsoup', version = '1.2')
class MyClass{...

我正在尝试对此类进行单元测试,但每当我尝试运行JUnit 4测试时,都会收到此错误:

Caused by: java.lang.RuntimeException: No suitable ClassLoader found for grab
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
    at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:102)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:52)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:198)
    at groovy.grape.GrapeIvy.chooseClassLoader(GrapeIvy.groovy:163)
    at groovy.grape.GrapeIvy$chooseClassLoader.callCurrent(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:44)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:149)
    at groovy.grape.GrapeIvy.grab(GrapeIvy.groovy:227)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSite.invoke(PogoMetaMethodSite.java:225)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:51)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:44)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:153)
    at groovy.grape.GrapeIvy.grab(GrapeIvy.groovy:216)
    at groovy.grape.Grape.grab(Grape.java:131)
    at groovy.grape.Grape$grab.callStatic(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:165)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:173)
    at ammoscanner.AmmoScanner.<clinit>(AmmoScanner.groovy)
    ... 30 more

有什么想法吗?我正在使用groovy 1.7.5

8 个答案:

答案 0 :(得分:5)

使用@Grab会使代码无法实现,至少从2011年1月26日开始。

答案 1 :(得分:5)

问题

Looking at the source code,只要提供的ClassLoader名称(或它的超类)不是groovy.lang.GroovyClassLoaderorg.codehaus.groovy.tools.RootLoader,就会抛出此异常。即目标类加载器必须是上述类的实例(有点限制性的恕我直言)。

解决方案

目前,我不知道如何使用@Grape / @Grab / @GrabConfig注释配置特定的类加载器。最接近的是使用@GrabConfig(systemClassLoader=true),并确保System类加载器是上述ClassLoader类之一的实例。

如果有人知道,请告诉我(我会更新此答案)。

解决方法

以下代码将以编程方式下载您的Grapes,并将它们加载到提供的GroovyClassLoader中(诚然,这不是您想要的)。

def loadGrapes(){
    ClassLoader classLoader = new groovy.lang.GroovyClassLoader()
    Map[] grapez = [[group : 'org.ccil.cowan.tagsoup', module : 'tagsoup', version : '1.2']]
    Grape.grab(classLoader: classLoader, grapez)
    println "Class: " + classLoader.loadClass('org.ccil.cowan.tagsoup.jaxp.SAXParserImpl')
}

答案 2 :(得分:2)

我假设您已尝试添加

@GrabConfig(systemClassLoader=true)
像这样:

@Grapes([
    @Grab(group = 'org.ccil.cowan.tagsoup', module = 'tagsoup', version = '1.2'),
    @GrabConfig( systemClassLoader=true )
])
class MyClass{...

答案 3 :(得分:1)

如果您没有使用systemClassLoader = true,那么您的IDE似乎没有使用groovy编译器运行代码,您可以使用一个简单的groovy类来检查它,该类输出其类加载器的类名。我猜它会尝试编译groovy类并使用非groovy类加载器运行它们。

另见this answer to General error during conversion: No suitable ClassLoader found for grab。另外this blog post解释了有关使用stock classloader运行预编译的groovy类的更多信息。

答案 4 :(得分:1)

对我有用的解决方案(用于在 IntelliJ 和通过 Maven 中使用 @Grab 运行脚本测试):

  1. 在您的 Maven @Grab 文件中引用通过 pom.xml 使用的依赖项(这对于更好的编码体验很有用):

例如我的 Groovy 脚本中有以下 @Grab

@Grab(group='info.picocli', module='picocli', version='4.6.1')

所以我添加了以下 Maven 依赖项:

    <dependency>
      <groupId>info.picocli</groupId>
      <artifactId>picocli</artifactId>
      <version>4.6.1</version>
    </dependency>
  1. 在您的 Maven ivy 文件中添加对 pom.xml 的可选依赖项(需要在您的 IDE 中正确处理 @Grab):
    <dependency>
      <groupId>org.apache.ivy</groupId>
      <artifactId>ivy</artifactId>
      <version>${ivy.version}</version>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>
  1. 在您的测试代码中,将 groovy.grape.enable 系统属性设置为 false这是解决方案的主要和关键部分 - 它禁用了脚本的 @Grab 注释处理,但请记住,我们已经在我们的 Maven pom.xml 文件中引用了这些依赖项:< /li>
    static {
        System.setProperty("groovy.grape.enable", "false")
    }

    @Test
    void test() {
        MainScript.call()
    }

该解决方案的缺点是您必须在 @Grab 和 Maven pom.xml 文件中复制您的依赖项,但同样,如果您开发 Groovy 脚本,您通常已经这样做以改善您的编码体验(获得更好的代码亮点等)。

答案 5 :(得分:0)

为开普勒添加plugin snapshot update site

这似乎解决了“..不合适的类加载器问题”。不幸的是,在此之后我仍然必须将葡萄回购添加到项目的类路径中。

答案 6 :(得分:0)

还有一个解决方案用于测试具有@Grab注释的类:

  1. 从此课程中提取界面。
  2. 创建另一个实现其接口的类。将@Grab注释移动到此类。然后使这个类成为一个简单的包装器,它只将所有消息传递给原始类。
  3. 针对原始课程运行测试。
  4. 每当您需要版本@Grab时,请使用包装器。

答案 7 :(得分:0)

对此有解决方案!

您可以使用Groovy的元编程来重写负责确定类加载器是groovy.lang.GroovyClassLoader还是org.codehaus.groovy.tools.RootLoader实例的方法。

由于this的Groovy错误,您无法使用元编程覆盖私有方法,否则您可以继续执行以下操作来更改isValidTargetClassLoaderClass方法:

GrapeIvy.metaClass.isValidTargetClassLoaderClass = { Class loaderClass ->
    return (loaderClass != null)
}

但是,isValidTargetClassLoaderClass是由isValidTargetClassLoader(另一个私有方法)调用的,而chooseClassLoader是由{em> public 方法(即 >可以使用元编程来覆盖:

GrapeIvy.metaClass.chooseClassLoader = { Map args ->
    def loader = args.classLoader
    if (loader?.class == null) {
        loader = (args.refObject?.class
                ?: ReflectionUtils.getCallingClass(args.calleeDepth?:1)
        )?.classLoader
        while (loader && loader?.class == null) {
            loader = loader.parent
        }

        if (loader?.class == null) {
            throw new RuntimeException("No suitable ClassLoader found for grab")
        }
    }
    return loader
}

我所做的就是用!isValidTargetClassLoader替换对loader?.class == null的所有呼叫。

现在,我正在使用Spock,因此将这段代码放在测试类的setupSpec方法中。但是,如果您使用的是JUnit,我想它会进入使用@BeforeClass注释的方法。

这是它与Spock一起使用的一个示例(注意IntelliJ显示它即将返回通常会引发异常的类加载器:

enter image description here