即使使用 DELEGATE_FIRST/DELEGATE_ONLY 解析策略,也无法在闭包中隐式委托方法

Can't delegate methods within closure implicitly even with DELEGATE_FIRST/DELEGATE_ONLY resolve strategy

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

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()
                }
            }

        }

    }
}

然而,当我在域 class 上调用此方法时,我必须使用 delegate.findByXXX() 否则 groovy 使用 owner 即使我已经明确设置了闭包将策略解析为 DELEGATE_FIRST.

我做错了什么?

...otherwise groovy uses owner even though I've explicitly set the closure resolve strategy to DELEGATE_FIRST.

我认为这不正确。

您有以下内容:

callable.delegate = delegate

它位于您分配给 withDataSource 的闭包内部。

当您以这种方式使用 ExpandoMetaClass 时,闭包的委托将是您调用该方法的对象。在您的情况下,它将是您调用 withDataSource 的东西。

你是对的,问题出在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"
    }
}

堆栈跟踪看起来像这样:

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.dgm7.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.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 反映了这个问题。

解决方法

好消息!这个问题有一个解决方法。而且非常简单:不使用 class 作为闭包的委托,而是使用 包装 class 的实例;一个代理。

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(),这是动态查找器所依赖的,在实例中运行良好,所以代理利用了这一点,并简单地调用你在它上面调用的任何方法,在真正的 Class 上。在本例中是域 class。我不确定您是否需要更改默认的解析策略,但我不这么认为。在我的测试中,这是不必要的。