dimanche 26 avril 2015

How to call a simple getter method in Groovy?

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