No suitable classloader found for grab

2019-01-15 06:35发布

问题:

I have this at the beginning of a class:

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

I'm trying to unit test this class, but whenever I try to run JUnit 4 tests, I get this error:

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

Any ideas? I'm using groovy 1.7.5

回答1:

Using @Grab makes code untestable, at least as of 01/26/2011.



回答2:

The Problem

Looking at the source code, this exception is thrown whenever the supplied ClassLoader's name (or it's superclasses) is not groovy.lang.GroovyClassLoader or org.codehaus.groovy.tools.RootLoader. i.e. The target classloader must be an instance of the aforementioned classes (a bit restrictive IMHO).

A Solution

Currently I don't know how to configure a specific classloader using @Grape/@Grab/@GrabConfig annotations. The closest would be to use @GrabConfig(systemClassLoader=true), and ensure the System classloader is an instance of one of the above ClassLoader classes.

If anyone does know, please let me know (and I'll update this answer).

A Workaround

The following code will programmatically download your Grapes, and load them into the supplied GroovyClassLoader (admittedly, not quite what you want).

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')
}


回答3:

I assume you've tried adding

@GrabConfig(systemClassLoader=true)

like so:

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


回答4:

If you are not using systemClassLoader=true then it seems your IDE is not rrunning the code with a groovy compiler, you can check that with a simple groovy class that outputs the class name of its classloader. I would guess it tries to compile the groovy classes and run them with a non-groovy classloader.

See also this answer to General error during conversion: No suitable ClassLoader found for grab. Also this blog post explains more about running pre-compiled groovy classes with the stock classloader.



回答5:

Add the plugin snapshot update site for Kepler.

This seems to solve the "..no suitable classloader problem". Unfortunately, I still had to add the grape repo to the classpath for the project after this.



回答6:

There's one more solution for testing a class with @Grab annotation:

  1. Extract an interface from this class.
  2. Create another class which implements its interface. Move the @Grab annotation to this class. Then make this class a simple wrapper, which just passes all the messages to the original class.
  3. Run the tests against your original class.
  4. Whenever you need to have a version @Grab, use the wrapper.