这两天掉入了一个Groovy metaClass的陷阱,好不容易才爬出来。
为了说清楚这个问题,先看几行测试代码
void testMetaClass() {
def domain = new Domain()
domain.metaClass.p = 'v1'
assertEquals 'v1', domain.p
domain.p = 'v2'
assertEquals 'v2', domain.p
domain.metaClass.p = 'v3'
assertEquals 'v2', domain.p
}
这是一个已经通过了的测试:
-------------------------------------------------------
Running 1 unit test...
Running test com.grs.GroovyMetaClassTests...null
Empty test suite.
PASSED
Tests Completed in 359ms ...
-------------------------------------------------------
注意Groovy测试代码的最后两行:
domain.metaClass.p = 'v3' assertEquals 'v2', domain.p
乍一看挺让人纳闷的: 执行
domain.metaClass.p = 'v3'
之后,为什么domain.p的值仍然是'v2'呢?这就是我称之为“陷阱”的地方了。看来Groovy中似乎有这么一条规则:往GroovyObject中第一次通过
domain.metaClass.aProperty = aValue
的方式往domain中注入aProperty之后, 再执行
domain.metaClass.aProperty = yetAnotherValue
则无法更改domain.aProperty,而
domain.aProperty = yetAnotherValue
却可以成功更改。
爬出这个陷阱,让我们来看这个问题该如何解决:我怎么才能知道aProperty有没有已经注入呢?换言之,我怎么知道我是应该写成
domain.metaClass.aProperty = yetAnotherValue
呢,还是应该写成
domain.aProperty = yetAnotherValue
呢?
我有两个workarounds:
1. 通过try...catch...来检测
class GroovyFieldAccessor {
static void set(String fieldName, Object obj, Object v) {
def metaPropAlreadyInjected = true
try {
if(obj."$fieldName") {} // do nothing but check if d."$fieldName" will throw an exception.
} catch(x) {metaPropAlreadyInjected = false}
if(metaPropAlreadyInjected) {
obj."$fieldName" = v
} else {
obj.metaClass."$fieldName" = v
}
}
}
2. 每次都两句一起写:
obj.metaClass."$fieldName" = v obj."$fieldName" = v
可保万无一失。
个人比较喜欢后者,因为它就两行,够简洁。
本文探讨了GroovyMetaClass使用过程中遇到的一个陷阱,并提供了两种解决方法:使用try...catch...来检测属性是否已注入,或者每次都同时使用obj.metaClass.$fieldName=v 和 obj.$fieldName=v来确保属性更改。
5062

被折叠的 条评论
为什么被折叠?



