Reputation: 1275
ClassNotFound (yes, I know, there are lots of posts about this exception; I searched here and elsewhere and could not find an explanation)
Why does Class.forName fail?
groovy> class Foo {
groovy> }
groovy> def f = new Foo()
groovy> def cname = f.getClass().getName()
groovy> def p = f.getClass().getPackage()
groovy> def l = f.getClass().getClassLoader()
groovy> println "Foo class name: $cname"
groovy> println "Foo package: $p"
groovy> println "Foo class loader: ${f.getClass().getClassLoader().toString()}"
groovy> println "Current class loader: ${this.getClass().getClassLoader().toString()}"
groovy> try {
groovy> Class.forName(cname)
groovy> } catch (Exception e) {
groovy> println e
groovy> }
groovy> l.findClass("Foo")
Foo class name: Foo
Foo package: null
Foo class loader: groovy.lang.GroovyClassLoader$InnerLoader@2d275595
Current class loader: groovy.lang.GroovyClassLoader$InnerLoader@2d275595
java.lang.ClassNotFoundException: Foo
Exception thrown
Oct 16, 2012 4:43:28 PM org.codehaus.groovy.runtime.StackTraceUtils sanitize
WARNING: Sanitizing stacktrace:
java.lang.ClassNotFoundException: Foo
Thanks!
Upvotes: 4
Views: 2122
Reputation: 6518
The given answer is basically right, it lacks one important information bit though. Class.forName(String)
is a Java method for Java to be called from Java. It needs to get the class loader to load the given class. And it gets the loader by using an internal method to walk up the call stack. While for java going up one level is the right thing to do normally it is not right in Groovy. Each method call in Groovy can contain a variable number of intermediate call stack elements from generated methods, from invokedynamic, from reflection. But usually the parent call stack frame is not containing the real caller class. Instead you end then up in a loader for the groovy runtime or even in the loader for the java runtime. The class in your shell is in a child of those, thus it is impossible for the loader to find the requested class.
Upvotes: 3
Reputation: 6733
This is due to the ClassLoader. The ClassLoader inside the the shell (ie the classes you define inside the shell) is different from the ClassLoader that runs the shell (the jars that you need to run the shell). That is why, the command Class.forName("Foo", true, this.class.classLoader)
works, because you specify the ClassLoader inside the shell
try
def shell=new GroovyShell()
def f=shell.evaluate("class Foo{Foo(){println this.class.classLoader}};def f=new Foo()")
println shell.class.classLoader
shell.evaluate("println this.class.classLoader")
println "-----------"
println Class.forName("Foo", true, f.class.classLoader)
println Class.forName("Foo", true, this.class.classLoader)
You will see that the first Class.forName works, not the second. running the script is similar because it will create a script class that does not share the shell's ClassLoader
Doing Class.forName won't use the same this as this in the context of your script.
Not sure it is clear enough :(
Upvotes: 2