Groovy 在propertyMissing方法中添加属性时的奇怪行为

Groovy 在propertyMissing方法中添加属性时的奇怪行为,groovy,Groovy,我希望在尝试设置类的过程中,在不使用动态属性的map的情况下,从功能上实现它,从而允许向类添加未知属性 由于Groovy允许使用元类来实现它,我在属性missing方法中使用了它 class Item { def propertyMissing(String name, value) { this.class.metaClass."$name" = value } } 但是我遇到了一个奇怪的行为 def i1 = new Item() i1.prop = "v

我希望在尝试设置类的过程中,在不使用动态属性的
map
的情况下,从功能上实现它,从而允许向类添加未知属性

由于Groovy允许使用
元类
来实现它,我在
属性missing
方法中使用了它

class Item {
    def propertyMissing(String name, value) {
        this.class.metaClass."$name" = value
    }
}
但是我遇到了一个奇怪的行为

def i1 = new Item()

i1.prop = "value"
println i1.properties // [class:class Item]
println i1.prop // null

i1.metaClass.field = "555"
println i1.properties // [prop:null, class:class Item, field:555]
println i1.prop // null

i1.prop = "value1"
println i1.properties // [prop:value1, class:class Item, field:555]
println i1.prop // value1
此外,如果我在尝试设置示例中的
prop
之前访问
metaClass
,它将不再添加它

def i1 = new Item()
i1.metaClass.unkn = "1111"

i1.prop = "value"
println i1.properties // [class:class Item, unkn:1111]
println i1.prop // null

i1.metaClass.field = "555"
println i1.properties // [class:class Item, unkn:1111, field:555]
println i1.prop // null

i1.prop = "value1"
println i1.properties // [class:class Item, unkn:1111, field:555]
println i1.prop // null

为什么它有这样的行为?

您遇到的问题之一是,您试图将属性添加到类
元类
,而不是实例
元类
。因为您在创建实例后添加属性,所以实例看不到它。例如,此代码无法打印属性:

class A { }

def a = new A()

A.metaClass.prop = 'value'

println a.prop
这个错误相当有趣:
groovy.lang.MissingPropertyException:没有这样的属性:类的prop:A
可能的解决方案:道具

但是,即使将代码更改为使用实例
元类
,它仍然不起作用:

class Item {
    def propertyMissing(String name, value) {
        metaClass."$name" = value
    }
}

def i1 = new Item()

i1.prop = 'value'
assert i1.prop == 'value'
该错误提供了一个线索:

groovy.lang.MissingPropertyException: No such property: prop for class: groovy.lang.MetaClassImpl
提供类
映射
功能的
元类是
ExpandoMetaClass
。对象通常不会获得这种类型的
元类
,除非您执行以下操作:

instance.metaClass.prop = 'value'
因此,
元类
不是
ExpandoMetaClass
这一事实意味着替换过程没有发生。在MOP过程中调用
propertyMissing()
可能太晚,无法以这种方式使用
元类

您提到要添加属性而不使用动态属性的
映射。然而,
ExpandoMetaClass
,这是您试图间接使用的,它使用

Map
s的动态属性!你可以看到

实现所需行为的最简单方法是扩展
Expando

class Item extends Expando {
    def anotherProperty = 'Hello'
}

def i1 = new Item()

i1.prop = 'value'
assert i1.prop == 'value'
assert i1.anotherProperty == 'Hello'

Expando
为您完成所有工作。如果您想了解它是如何工作的,请阅读。

当您动态更新对象的元类时,Groovy会将元类替换为ExpandoMetaClass。它是元类的特殊实现,支持添加和删除属性/方法

但是,在您的示例中,
Item
是一个
GroovyObject
,它在元类上有一个持久字段。交换元类时,此字段不会更新:只有注册表中的元类被ExpandoMetaClass替换。这类代码可以与javaobject一起工作,因为该对象没有字段,每次groovy访问元类时都会执行resolution class->metaclass

如果您知道要在groovy对象上添加属性,则应显式设置ExpandoMetaClass:

class Item {

    def Item() {
      def mc = new ExpandoMetaClass(Item, false, true)
      mc.initialize()
      this.metaClass = mc
    }

    def propertyMissing(String name, value) {
        this.metaClass."$name" = value
    }  
}  

哦,我明白了。我完全忽略了MetaClassImpl的异常。我只是看到丢失的属性异常,没有进一步查看。。。