groovy - 在 Groovy 中覆盖元类属性

标签 groovy metaprogramming

我想覆盖元类上的动态属性,但它没有按我的预期工作。这是一个演示该问题的简单示例。您可以在线运行它 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()

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

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官方文档中所述使用元类的 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 中覆盖元类属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50745518/

相关文章:

c++ - 根据参数返回类型

javascript - JavaScript 中有没有一种方法可以实例化不一定是函数的对象?

c++ - 提升融合奇数

jenkins 共享库错误 com.cloudbees.groovy.cps.impl.CpsCallableInvocation

Groovy 反向映射键

grails - Grails NumberFormatException

c# - 如何使用泛型在 C# 中将类型静态绑定(bind)在一起(如 TypeToType<T>)?

java - 评估后检索使用的常规变量

groovy - 转换为 bool 值时,groovy 会隐式调用 Matcher 上的查找吗?

ruby-on-rails - 通过 mixin 将私有(private)方法添加到类中