【问题标题】:Groovy mixin unable to see its properties when invokeMethod is involved当涉及到 invokeMethod 时,Groovy mixin 无法查看其属性
【发布时间】:2012-03-02 06:46:49
【问题描述】:

当我将 invokeMethod 方法放到其中一个 mixin 类上时,我看到了一些我无法用 Groovy (1.8) mixins 解释的东西。

下面的测试证明了这种效果:

 1: import java.net.Socket
 2:
 3: import org.junit.Test
 4: import static org.junit.Assert.*
 5: 
 6: class MixinPropertyTest {
 7:     static class Foo {
 8:         def message
 9:        
10:         Object invokeMethod(String name, args) {
11:             if (name != "println") {
12:                 println "invokeMethod sees ${message}"
13:                 println "invoking ${name}"
14:             }
15:             def metaMethod = metaClass.getMetaMethod(name, args)
16:             metaMethod?.invoke(this, args)
17:         }
18:         
19:         String message() {
20:             message
21:         }
22:     }
23:     
24:     @Mixin(Foo)
25:     static class Bar {
26:     }
27:     
28:     @Test
29:     void test() {
30:         assertEquals 'hello', new Bar(message: 'hello').message()
31:     }
32: }

此测试失败,输出如下:

invokeMethod sees hello
invoking message

但是,如果我剪掉 invokeMethod,它就会通过。 invokeMethod 在那里会导致它停止工作吗?

编辑:如果我在第 15 行和第 20 行设置断点,我将看到 this 分别为 MixinPropertyTest$Foo (id=43)MixinPropertyTest$Foo (id=75)。在MetaMethod.invoke 调用过程中,我似乎正在与更改交互的Foo 实例。

【问题讨论】:

    标签: groovy mixins


    【解决方案1】:

    使用invokeMethod 根据Using invokeMethod and getProperty 拦截所有方法和属性调用。如果你想让它传递给message(),你应该添加一个 if 子句来检查方法名称是否是“消息”或将invokeMethod 更改为methodMissing

    编辑:

    由于您试图拦截呼叫,我认为问题可能是转发invoke 没有获得正确的元方法。也就是说,由于您使用来自thismetaClass,您将获得MixinInstanceMetaMethod,因为this.metaClass 指的是MixedInMetaClass。鉴于您拥有 MetaMethod,它应该在元类所有者(应该是 Bar)上调用。

    以下代码应该可以工作:

    import org.junit.Test
    import static org.junit.Assert.*
    
    class MixinPropertyTest {
        static class Foo {
            def message
    
            def invokeMethod(String name, args) {
                System.out.println "invokeMethod sees ${message}"
                System.out.println "invoking ${name}"
    
                def metaMethod = this.metaClass.getMetaMethod(name, args)
                metaMethod?.invoke(this.metaClass.owner, args)
            }
    
            String message() {
                message
            }
        }
    
        @Mixin(Foo)
        static class Bar {
        }
    
        @Test
        void test() {
            assertEquals 'hello', new Bar(message: 'hello').message()
        }
    }
    

    进一步澄清:我认为因为您传递给 metaMethod.invoke 的 this 已经是 mixin 实例,this 更改的原因是因为 metaMethod 是一个 MixinInstanceMetaMethod,它试图查找混合实例。如果没有找到它,它会创建一个新的 mixin 实例,这就是为什么对 this 的引用不同于 invokeMethodmessage 方法的原因。

    This line in the Groovy source 表明调用 invoke 会查找 mixin 实例。

    源代码中的这一行显示了新 mixin 实例的创建:https://github.com/groovy/groovy-core/blob/master/src/main/org/codehaus/groovy/reflection/MixinInMetaClass.java#L68

    【讨论】:

    • 拦截现有方法完全是有意的,因此使用methodMissing() 或以某种方式排除对message() 的调用将不是合适的解决方案。使用调试器,我可以看到对message() 的调用正确调度,但无论出于何种原因,message() 中的this 最终与invokeMethod() 中的Foo 实例不同,这让我感到困惑。
    【解决方案2】:

    我设法用调试器解决了这个问题。

    因为invokeMethod() 使用thisFoo 的实例)而不是拥有该mixin 的对象,所以MixinInMetaClass.getMixinInstance() 在幕后决定创建Foo 的新实例。 (它基于外部对象缓存 mixin 实例,它认为我在 Foo 的实例中寻找 Foo 的 mixin 实例。)

    我似乎能够通过注意何时在 OwnedMetaClass 的实例上调用 invokeMethod 来解决此问题:

    class Foo {
        // ...
        Object invokeMethod(String name, args) {
            def target = this
            if (metaClass instanceof OwnedMetaClass) {
                target = metaClass.owner
            }
            MetaMethod metaMethod = target.metaClass.getMetaMethod(name, args)
            metaMethod?.invoke(target, args)
        }
        // ...
    }
    

    这似乎应该由 Groovy 自动处理。要求类在 invokeMethod 中有额外的代码才能被用作 mixin,这似乎不合常规。

    【讨论】:

    • 哈,我刚刚浏览了 Groovy 源代码,发现了您在调试器中看到的内容。我同意处理这个不是很时髦。也许应该打开一个错误?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-14
    • 2018-02-19
    • 2014-03-06
    相关资源
    最近更新 更多