【问题标题】:Overwrite a metaClass property in Groovy在 Groovy 中覆盖 metaClass 属性
【发布时间】:2018-11-17 14:55:50
【问题描述】:

我想覆盖元类上的动态属性,但它没有像我预期的那样工作。这是一个演示问题的简单示例。你可以在Groovy web console在线运行它

class Bike {}
class Bell { def ring() { println("calling " + this) } }

def createNamespaces = {
    return [ "bell": new Bell() ]
}

def resetNamespaces = { bike ->
    // setting to null means 'set it to the default'
    bike.metaClass = null

    createNamespaces().each { name, namespace ->
        println("setting " + namespace)
        bike.metaClass."$name" = namespace
    }
}

def bike= new Bike()

resetNamespaces(bike)
bike.bell.ring()
resetNamespaces(bike)
bike.bell.ring()

结果是:

setting Bell@14e9bd2b
calling Bell@14e9bd2b
setting Bell@948a7ad
calling Bell@14e9bd2b

因此,尽管我更改了元类的属性,但调用它总是返回设置的第一个对象。是否有某种缓存?

当我将示例的最后一部分更改为:

resetNamespaces(bike)
resetNamespaces(bike)
bike.bell.ring()

那么结果如预期。也就是说,调用该属性会返回最后在 metaClass 上设置的对象:

setting Bell@5b47e0c7
setting Bell@19f373a4
calling Bell@19f373a4

我什至尝试如下手动设置元类

def resetNamespaces = { bike ->
    def newMetaClass = new ExpandoMetaClass(Bike.class, true, true)
    newMetaClass.initialize()

    bike.metaClass = newMetaClass

    ...
}

但结果还是一样。所以肯定有一些缓存机制。我在文档中找不到有关此行为的任何信息。

【问题讨论】:

    标签: groovy metaprogramming


    【解决方案1】:

    因为您试图更改元类的属性,而不是方法,所以会出现混淆。正如groovy metaprogramming 的官方文档中所述,属性是使用元类的 setAttribute/getAttribute 方法访问的。

    // throws MissingFieldException!
    def resetNamespaces = { bike ->
        createNamespaces().each { name, namespace ->
            bike.metaClass.setAttribute(bike, name, namespace)
        }
    }
    

    不幸的是,这只适用于属性在原始类定义中的情况,这里它会抛出一个MissingFieldException: No such field: bell for class: Bike

    另一方面,重写方法就像一个魅力,并通过添加动态 getter 方法来提供所需的语法:

    def resetNamespaces(bike) {
        createNamespaces().each { name, namespace ->
            bike.metaClass."get${name.capitalize()}" = { namespace }
        }
    }
    
    def bike = new Bike()
    resetNamespaces(bike)
    bike.bell.ring()
    resetNamespaces(bike)
    bike.bell.ring()
    

    其实expando也很好用

    class Bell {
        def ring() { println this }
    }
    
    def bike = new Expando()
    bike.createNamespaces = {
        return [ bell : new Bell() ]
    }
    
    bike.resetNamespaces = {
        bike.createNamespaces().each { name, namespace ->
            bike."$name" = namespace
        }
    }
    
    bike.resetNamespaces bike
    bike.bell.ring()
    bike.resetNamespaces bike
    bike.bell.ring() 
    

    【讨论】:

    • 所以你说我不能改变一个对象实例的元类的动态属性?它是在文档中还是在源代码中的某个地方,以便我可以找到它?我认为对 groovy 对象的每个方法/属性调用都委托给它的元类。请参阅我的问题中添加的最后一部分。
    • 对不起,你完全正确,当然你可以覆盖元类......见我编辑的答案。无法提供对 groovy 内部结构的更深入见解,但我希望它有所帮助。
    猜你喜欢
    • 2010-12-28
    • 1970-01-01
    • 2012-02-10
    • 1970-01-01
    • 2016-06-12
    • 1970-01-01
    • 2017-10-30
    • 1970-01-01
    • 2021-09-20
    相关资源
    最近更新 更多