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