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

时间:2016-03-07 13:13:06

标签: groovy

我想在功能上实现,允许在尝试设置它的同时向类添加未知属性,而不使用map动态属性。

由于Groovy允许使用metaClass执行此操作,因此我在propertyMissing方法中使用了它。

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

此外,如果我在尝试在示例中设置metaClass之前访问prop,则不再添加

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

为什么会出现这种行为?

2 个答案:

答案 0 :(得分:2)

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

但是,在您的示例中,ItemGroovyObject,它在MetaClass上有一个持久字段。交换MetaClass时不更新此字段:只有注册表中的元类被ExpandoMetaClass替换。这种代码可以使用javaobject,因为这个对象没有字段,每次groovy访问元类时都会执行分辨率class->元类。

你知道要在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
    }  
}  

答案 1 :(得分:1)

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

class A { }

def a = new A()

A.metaClass.prop = 'value'

println a.prop

错误很有趣:groovy.lang.MissingPropertyException: No such property: prop for class: A Possible solutions: prop

但是,即使您更改代码以使用实例MetaClass,它仍然无效:

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

提供类似MetaClass功能的MapExpandoMetaClass。在您执行以下操作之前,对象通常不会获得此类MetaClass

instance.metaClass.prop = 'value'

因此MetaClass不是ExpandoMetaClass的事实意味着没有发生替换过程。在MOP过程中,propertyMissing()被调用的可能性太晚,无法以这种方式使用MetaClass

您提到要使用Map动态属性添加不带的属性。但是,ExpandoMetaClass,这是您尝试间接使用的,使用...

... Map的动态属性!你可以看到它here

实现您正在寻找的行为的最简单方法是扩展 Expando

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

def i1 = new Item()

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

Expando为您完成所有工作。如果您想了解其工作原理,请阅读this