How to call a simple getter method in Groovy?

I can’t believe that I have to ask this question, it’s really confusing. The purpose of this question is to find out why and / or find a simpler way (than reflection) to get the correct result.

Background story

I downloaded some class files from directories and jar files through URLClassLoader, and I would like to reset all class names and their declared methods. Classes cannot be initialized (see false), because if any code is executed in these classes, exceptions may be thrown due to some missing dependencies. I ran into this problem trying to print only class names.

Question

What am I missing, how can I get around Groovy magic and just call a method (called getName) on an object (type java.lang.Class) without reflection? Someone will also point out a specification as to why this works this way.
Here is 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 thrown SHOULD NOT INIT

["name"] // this is just plain magic! What a Terrible Failure :)
name and thrown SHOULD NOT INIT

reflection // obviously works, becase it really explicit
X and Y

Wiring harness and control samples

Changing the test closure, which should be explicit for the argument ( Class<?> c ->) type, does not matter.

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("SHOULD NOT INIT");
        }
    }
    ''');
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 no need to init
    // as shown in the reflection test.
    // Even fields and methds can be read without initializing,
    // it 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 "thrown " + ex.cause?.message
    }
    print " and "
    try {
        print nameOf(y)
    } catch (Throwable ex) {
        print "thrown " + 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); }
+4
source share
1 answer

For the invokedynamic port, I can give a clear answer: bypass the JVM error in which the JVM made a call to the static method of the class without calling the clinic at all.

... commit https://github.com/apache/incubator-groovy/commit/0653ddc15ec0215f2141159a71c1b12d8d800dbe#diff-59caa62540f88da51c8c91c6656315d5 , . , JVM , , ...

0

Source: https://habr.com/ru/post/1584768/


All Articles