I can't believe I have to ask this question, it's really confusing. The goal of this question is to figure out why and/or find an easier way (than reflection) to get the correct result.
Back story
I loaded some class files from directories and jar files via a URLClassLoader and I'd like to dump all the class names and their declared methods. The classes cannot be initialized (see false) because if any code runs in those classes exceptions may be thrown due to some missing dependencies. I ran into this problem while trying to output just the class names.
Question
What am I missing, how can I work around Groovy's magic and just simply call a method (called getName) on an object (of type java.lang.Class) without reflection? Someone also please point to the specification as to why this work this way.
Here's the output for my mini-test (see below) and my comments:
.name // magically initializes class X and calls X.get("name")
name and Y
.getName() // tries to reload class Y in another ClassLoader and initialize it
X and NO INITIALIZATION
["name"] // this is just plain magic! What a Terrible Failure :)
name and NO INITIALIZATION
reflection // obviously works, becase it's really explicit
X and Y
Test harness and test-cases
Changing the test closures to be explicit on the argument type (Class<?> c -> makes no difference.
new File("X.java").write('''
public class X {
public static String get(String key) {
return key;
}
}
''');
new File("Y.java").write('''
public class Y {
static {
if (true) // needed to prevent compile error
throw new UnsupportedOperationException("NO INITIALIZATION");
}
}
''');
print 'javac X.java Y.java'.execute().err.text;
def test = { String title, Closure nameOf ->
URL url = new File(".").toURI().toURL();
// need a new ClassLoader each time because it remembers
// if a class has already thrown ExceptionInInitializerError
ClassLoader loader = java.net.URLClassLoader.newInstance(url);
// false means not to initialize the class.
// To get the name of the class there's no need to init
// as shown in the reflection test.
// Even fields and methds can be read without initializing,
// it's essentially just parsing the .class file.
Class x = Class.forName("X", false, loader);
Class y = Class.forName("Y", false, loader);
println()
println title
try {
print nameOf(x)
} catch (Throwable ex) {
print ex.cause?.message
}
print " and "
try {
print nameOf(y)
} catch (Throwable ex) {
print ex.cause?.message
}
println()
}
test '.name', { c -> c.name; }
test '.getName()', { c -> c.getName(); }
test '["name"]', { c -> c["name"] }
test 'reflection', { c -> java.lang.Class.class.getDeclaredMethod("getName").invoke(c); }
Aucun commentaire:
Enregistrer un commentaire