从Groovy中的Closure修改脚本变量

时间:2017-09-29 18:23:58

标签: groovy

我正在尝试从函数中的闭包内部修改脚本变量。问题可以归结为:

@groovy.transform.Field int myField = 0

incrementField()
assert myField == 1

def incrementField() {
    1.times { myField++ }
}

我认为问题与closure delegates有关,但我无法完全理解文档。

2 个答案:

答案 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
}