Classloader вызывается перед missingProperty

#gradle #groovy

#gradle #groovy

Вопрос:

Допустим, у меня есть сценарий под названием Utils.groovy :

 package hello

def testMethod() {
    log.info("hello world")
}

def propertyMissing(String name) {
    System.out.println("Loading ${name}.groovy")
    GroovyScriptEngine gse = new GroovyScriptEngine("vars")
    gse.loadScriptByName("${name}.groovy").newInstance()
}

 

Он ссылается на свойство, которого не существует, log . Однако log существует как класс, который будет загружен скриптовым движком Groovy:

 void info(Object message) {
    println(message)
}
 

Если Utils.groovy сам загружается с помощью GroovyScriptEngine, он вызовет propertyMissing метод и динамически загрузится log.groovy .

Вот пример кода, который загружается Utils.groovy :

 package hello

class Main {
    static void main(String... args) {
        GroovyScriptEngine gse = new GroovyScriptEngine("src")
        Class<Script> scriptClass = gse.loadScriptByName("hello/Utils.groovy")
        def utils = scriptClass.newInstance()

        //def utils = new Utils()

        utils.testMethod()
    }
}
 

И вот результат:

 Loading log.groovy
hello world
 

Теперь, вместо того, чтобы загружаться GSE, давайте создадим новый экземпляр Utils.groovy и выполним его напрямую (закомментировав первые три строки в приведенном выше примере и раскомментировав следующую строку):

 package hello

class Main {
    static void main(String... args) {
        //GroovyScriptEngine gse = new GroovyScriptEngine("src")
        //Class<Script> scriptClass = gse.loadScriptByName("hello/Utils.groovy")
        //def utils = scriptClass.newInstance()

        def utils = new Utils()

        utils.testMethod()
    }
}
 

Однако ему не удается вызвать propertyMissing метод в Utils.groovy и вместо этого он пытается загрузить log.groovy его непосредственно из classpath. Однако. поскольку info это не статический метод, если сбой с этой ошибкой:

 Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: static log.info() is applicable for argument types: (java.lang.String) values: [hello world]
Possible solutions: info(java.lang.Object), find(), any(), find(groovy.lang.Closure), is(java.lang.Object), any(groovy.lang.Closure)
    at groovy.lang.MetaClassImpl.invokeStaticMissingMethod(MetaClassImpl.java:1518)
    at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:1504)
    at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:52)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)
    at hello.Utils.testMethod(Utils.groovy:4)
    at hello.Utils$testMethod.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)
    at hello.Main.main(Main.groovy:11)
 

Однако. Я заметил, что если я изменю имя log свойства на что-то, чего не существует в пути к классу ( loggy.info например, и сохраню log.groovy имя файла таким же), это приведет к вызову propertyMissing метода (но завершается ошибкой, потому что он не может найти несуществующий скрипт):

 Loading loggy.groovy
Exception in thread "main" groovy.util.ResourceException: Cannot open URL: file:/home/wlaw/test/vars/loggy.groovy
    at groovy.util.GroovyScriptEngine.getResourceConnection(GroovyScriptEngine.java:408)
    at groovy.util.GroovyScriptEngine.loadScriptByName(GroovyScriptEngine.java:552)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap.invoke(PojoMetaMethodSite.java:213)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)
    at hello.Utils.propertyMissing(Utils.groovy:10)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
    at groovy.lang.MetaClassImpl.invokeMissingProperty(MetaClassImpl.java:890)
    at groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:1854)
    at groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:3773)
    at groovy.lang.GroovyObjectSupport.getProperty(GroovyObjectSupport.java:38)
    at groovy.lang.Script.getProperty(Script.java:58)
    at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:310)
    at hello.Utils.testMethod(Utils.groovy:4)
    at hello.Utils$testMethod.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)
    at hello.Main.main(Main.groovy:11)
 

Таким образом, похоже propertyMissing , что метод будет вызван только в том случае, если 1. скрипт создается напрямую и ссылается на отсутствующее свойство, имени которого нет в пути к классу, или 2. скрипт загружается GSE.

Итак, мой вопрос в том, есть ли причина, по которой Groovy пытается сначала найти класс вместо вызова propertyMissing объектов, не загруженных GSE? И есть ли способ изменить это поведение?

Примечание: оба vars и src находятся в пути к классу проекта. Также обратите внимание: я запускаю эти файлы, сначала прогоняя их, groovyc а затем с java помощью .

Комментарии:

1. это вполне логично, если в classpath есть класс с именем log . java / groovy пытается загрузить этот класс.

2. я имею в виду, что classloader всегда идет перед propertyMissing . в одном случае вы не регистрируете класс в classpath. в случае ошибки в вашем пути к классу есть журнал классов.

3. Не обязательно. Загрузчик классов запускается первым только для классов, которые создаются непосредственно. Если он загружен с помощью GSE, сначала выполняется пропуск свойств.

4. Если вы попытаетесь String.format() — вы не создаете его экземпляр — как groovy должен решить, что String является классом, а не свойством? Я считаю, что загрузчик классов всегда идет первым.

5. Я говорю о строках в Main.groovy. Если экземпляр объекта создается напрямую ( def utils = new Utils() ), то его propertyMissing метод не вызывается первым для отсутствующих свойств, которые соответствуют имени класса в пути к классу. Если экземпляр объекта создается через GSE, сначала вызывается его метод propertyMissing. Это хорошо, что вы «верите», что загрузчик классов идет первым, но мой пример показывает обратное.


Ответ №1:

Если «log» является типом, он будет разрешен как таковой во время компиляции. Если ты хочешь «log.info (…)» чтобы всегда пытаться использовать динамическое свойство, вы можете написать «this.log.info (…)». Groovy позволяет вам писать литералы класса короче, поэтому «log» интерпретируется как «log.class «, что объясняет статический метод поиска «info».