在Groovy中覆盖元类属性

在Groovy中覆盖元类属性,groovy,metaprogramming,Groovy,Metaprogramming,我想覆盖元类上的动态属性,但它不能像我预期的那样工作。下面是一个简单的例子来说明这个问题。您可以在以下位置在线运行它: 结果是: setting Bell@14e9bd2b calling Bell@14e9bd2b setting Bell@948a7ad calling Bell@14e9bd2b 因此,尽管我更改了元类的属性,调用它总是返回设置的第一个对象。是否存在某种缓存 当我将示例的最后一部分更改为: resetNamespaces(bike) resetNamespaces(bik

我想覆盖元类上的动态属性,但它不能像我预期的那样工作。下面是一个简单的例子来说明这个问题。您可以在以下位置在线运行它:

结果是:

setting Bell@14e9bd2b
calling Bell@14e9bd2b
setting Bell@948a7ad
calling Bell@14e9bd2b
因此,尽管我更改了元类的属性,调用它总是返回设置的第一个对象。是否存在某种缓存

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

resetNamespaces(bike)
resetNamespaces(bike)
bike.bell.ring()
那么结果就如预期的那样了。也就是说,调用属性将返回在元类上设置为最后一个的对象:

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

    ...
}

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

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

// throws MissingFieldException!
def resetNamespaces = { bike ->
    createNamespaces().each { name, namespace ->
        bike.metaClass.setAttribute(bike, name, namespace)
    }
}
不幸的是,这仅在属性位于原始类定义中时有效,在这里它抛出一个
MissingFieldException:No-this-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内部提供更深入的见解,但我希望它能有所帮助。
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()