grails - 即使使用 DELEGATE_FIRST/DELEGATE_ONLY 解析策略,也无法隐式委托(delegate)闭包内的方法

标签 grails groovy grails-2.0

我有一个扩展所有域类的方法:

static def bindGormStaticApiExtensions() {
    Holders.grailsApplication.domainClasses.each { domainClass ->
        domainClass.metaClass.static.withDataSource = { DataSource ds, Closure callable ->
            HibernateDatastore datastore = Holders.applicationContext.getBean(HibernateDatastore)
            SessionFactory sessionFactory = datastore.sessionFactory
            Session session = null
            Connection connection = null
            try {
                SessionBuilder sb = sessionFactory.withOptions()
                connection = ds.getConnection()
                session = sb.connection(connection).openSession()
                callable.delegate = delegate
                callable.resolveStrategy = Closure.DELEGATE_FIRST
                return callable?.call(session)
            } catch (Exception e) {
                LOG.error("An error occured", e)
            } finally {
                session?.close()
                if(connection && !connection.closed) {
                    connection.close()
                }
            }

        }

    }
}

但是,当我在域类上调用此方法时,我必须使用 delegate.findByXXX()否则 groovy 使用 owner即使我已将闭包解决策略明确设置为 DELEGATE_FIRST .

我在这里做错了什么?

最佳答案

你是对的,问题出在 Groovy 中。这是一个演示问题的简单测试:

assert MyClass.thisMethodDoesNotExist() == 'You called static method thisMethodDoesNotExist'
assert new MyClass().thisMethodDoesNotExist() == 'You called instance method thisMethodDoesNotExist'

new MyClass().with {
    assert thisMethodDoesNotExistEither() == 'You called instance method thisMethodDoesNotExistEither'
}

MyClass.with {
    // The following method call will throw a groovy.lang.MissingMethodException
    assert thisMethodDoesNotExistEither() == 'You called static method thisMethodDoesNotExistEither'
}

class MyClass {
    static Object $static_methodMissing(String name, Object args) {
        "You called static method $name"
    }

    Object methodMissing(String name, Object args) {
        "You called instance method $name"
    }
}

stackstrace 看起来像这样:
groovy.lang.MissingMethodException: No signature of method: ConsoleScript10.thisMethodDoesNotExistEither() is applicable for argument types: () values: []
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:81)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:158)
    at ConsoleScript10$_run_closure2.doCall(ConsoleScript10:10)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1019)
    at groovy.lang.Closure.call(Closure.java:426)
    at groovy.lang.Closure.call(Closure.java:442)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.with(DefaultGroovyMethods.java:241)
    at org.codehaus.groovy.runtime.dgm$757.invoke(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite$StaticMetaMethodSiteNoUnwrapNoCoerce.invoke(StaticMetaMethodSite.java:151)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.call(StaticMetaMethodSite.java:91)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
    at ConsoleScript10.run(ConsoleScript10:8)
    at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:263)
    at groovy.lang.GroovyShell.run(GroovyShell.java:524)
    at groovy.lang.GroovyShell.run(GroovyShell.java:503)
    at groovy.lang.GroovyShell.run(GroovyShell.java:170)
    at groovy.lang.GroovyShell$run$1.call(Unknown Source)
    at groovy.ui.Console$_runScriptImpl_closure17.doCall(Console.groovy:980)
    at groovy.ui.Console$_runScriptImpl_closure17.doCall(Console.groovy)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1019)
    at groovy.lang.Closure.call(Closure.java:426)
    at groovy.lang.Closure.call(Closure.java:420)
    at groovy.lang.Closure.run(Closure.java:507)
    at java.lang.Thread.run(Thread.java:724)

我有我的怀疑,我对 Groovy 的 MOP 实现没有完全了解,所以我不知道如何解决这个问题。有一个开放的bug反射(reflect)了这个问题。

解决方法

好消息!有一个解决这个问题的方法。而且非常简单:不要使用类作为闭包的委托(delegate),而是使用
包装类的实例;代理。
static def bindGormStaticApiExtensions() {
    Holders.grailsApplication.domainClasses.each { domainClass ->
        domainClass.metaClass.static.withDataSource = { DataSource ds, Closure callable ->
            HibernateDatastore datastore = Holders.applicationContext.getBean(HibernateDatastore)
            SessionFactory sessionFactory = datastore.sessionFactory
            Session session = null
            Connection connection = null
            try {
                SessionBuilder sb = sessionFactory.withOptions()
                connection = ds.getConnection()
                session = sb.connection(connection).openSession()

                // Use a proxy as the delegate instead of the domain class.
                callable.delegate = new ClassProxy(delegate)
                callable?.call(session)

            } catch (Exception e) {
                LOG.error("An error occured", e)
            } finally {
                session?.close()
                if(connection && !connection.closed) {
                    connection.close()
                }
            }

        }

    }
}

这是代理:
// src/main/groovy/some/package/ClassProxy.groovy
@groovy.transform.TupleConstructor
/*
 * Create an instance of my like so: new ClassProxy(SomeClass)
 * and I will delegate method calls to the Class,
 * essentially converting instance method calls to Class static
 * method calls.
 */
class ClassProxy {
    Class clazz

    Object methodMissing(String name, Object args) {
        clazz.invokeMethod(name, args)
    }
}
methodMissing() ,这是动态查找器所依赖的,适用于实例,因此代理利用这一点并简单地调用您在真实类上调用的任何方法。在这种情况下是域类。我不确定您是否需要更改默认解析策略,但我不这么认为。在我的测试中,这是不必要的。

关于grails - 即使使用 DELEGATE_FIRST/DELEGATE_ONLY 解析策略,也无法隐式委托(delegate)闭包内的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36286924/

相关文章:

ssl - 来自 grails 应用程序的 handshake_failure

maven - Grails 3.x 修改 mavenLocal() 以定向到默认位置以外的另一个位置和名称

css - Grails bootstrap 隐藏打印无法按预期工作

java - 未找到 Spring Security 的 Spring Bean 还记得我吗?

grails - 从外部 grails 项目引用域对象 - 在 Grails 应用程序之外使用了类 [] 上的方法

sql-server - 从Grails访问SQL Server 2008 View

groovy - Gradle:获取传递依赖项并导出到最小的运行时 fat jar

Jenkins Groovy unix 脚本错误 java.lang.NoSuchMethodError : 'boolean mightBePositionalArgumentConstructor VariableExpression

python - 调试 Ruby/Python/Groovy

spring - ConversionNotSupportedException : Failed to convert property value of type 'grails.spring.BeanBuilder' to required type 'java.lang.String'