我正在尝试从函数中的闭包内部修改脚本变量。问题可以归结为:
@groovy.transform.Field int myField = 0
incrementField()
assert myField == 1
def incrementField() {
1.times { myField++ }
}
我认为问题与closure delegates有关,但我无法完全理解文档。
答案 0 :(得分:4)
此行为是由groovy.lang.Script
类引起的,并且它会覆盖以下方法:
示例中显示的Closure使用delegate
设置为脚本对象,这就是当您尝试访问或修改脚本中定义的字段时,两个重写方法都被执行的原因。
现在让我们看看当你的例子到达闭包时会发生什么
{ myField++ }
首先,调用getProperty("myField")
以返回与此属性关联的值。此方法实现为:
public Object getProperty(String property) {
try {
return binding.getVariable(property);
} catch (MissingPropertyException e) {
return super.getProperty(property);
}
}
来源:https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L54
binding
对象在开头只包含一个变量 - closure的args
数组。如果我们看一下binding.getVariable(property)
方法的实现,我们会看到:
public Object getVariable(String name) {
if (variables == null)
throw new MissingPropertyException(name, this.getClass());
Object result = variables.get(name);
if (result == null && !variables.containsKey(name)) {
throw new MissingPropertyException(name, this.getClass());
}
return result;
}
来源:https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Binding.java#L56
在我们的情况下,MissingPropertyException
被抛出,因此Script.getProperty(property)
方法返回我们的Groovy脚本中定义的字段myField
的值 - 0
。然后Groovy将此值递增1并尝试将此新值设置为字段myField
。在这种情况下,Script.setProperty(property, value)
被调用:
public void setProperty(String property, Object newValue) {
if ("binding".equals(property))
setBinding((Binding) newValue);
else if("metaClass".equals(property))
setMetaClass((MetaClass)newValue);
else
binding.setVariable(property, newValue);
}
来源:https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L62
如您所见,它使用bindings
对象设置此新值。如果我们显示binding.variables
,我们会看到现在这个内部地图包含两个条目:args -> [:]
和myField -> 1
。它解释了为什么脚本中的断言总是失败。您定义的闭包的主体永远不会从脚本类到达myField
字段。
如果您对Script
类重写setProperty(property, value)
方法不满意,您可以在脚本中手动覆盖它,并使用与GroovyObjectSupport.setProperty(property, value)
相同的实现。只需将以下方法添加到Groovy脚本:
@Override
void setProperty(String property, Object newValue) {
getMetaClass().setProperty(this, property, newValue)
}
现在incrementField
中定义的闭包将为类字段而不是bindings
对象设置新值。当然它可能会导致一些奇怪的副作用,你必须要注意这一点。我希望它有所帮助。
答案 1 :(得分:2)
找到一个可能的解决方案,使用闭包委托:
@groovy.transform.Field def stuff = [
myField : 0
]
incrementField()
assert stuff.myField == 1
def incrementField() {
def body = { myField++ }
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = stuff
1.times body
}