我正在尝试使用GroovyShell
创建一些动态变量,但遇到了一个问题。首先,工作代码:
static def defVar(def glob) {
glob.setVariable('test', new Test())
}
class MyBinding extends Binding {
}
class Test {
def call() {
println("--- hello ---")
}
}
Binding glob = new MyBinding()
GroovyShell shell = new GroovyShell(glob)
defVar(glob)
shell.parse('test()').run()
这给了我预期的输出:
--- hello ---
但是,我想在调用setVariable()
时动态调用getVariable()
,如下所示:
static def defVar(def glob) {
glob.setVariable('test', new Test())
}
class MyBinding extends Binding {
def getVariable(String name) {
if (! hasVariable('test')) {
BindingTest.defVar(this)
}
return super.getVariable(name)
}
}
class Test {
def call() {
println("--- hello ---")
}
}
Binding glob = new MyBinding()
GroovyShell shell = new GroovyShell(glob)
//defVar(glob)
shell.parse('test()').run()
但这失败并显示以下错误:
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
Possible solutions: getAt(java.lang.String), use([Ljava.lang.Object;), use(java.lang.Class, groovy.lang.Closure), use(java.util.List, groovy.lang.Closure), wait(), wait(long)
groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
Possible solutions: getAt(java.lang.String), use([Ljava.lang.Object;), use(java.lang.Class, groovy.lang.Closure), use(java.util.List, groovy.lang.Closure), wait(), wait(long)
at Script1.run(Script1.groovy:1)
at Script1$run.call(Unknown Source)
at BindingTest.run(BindingTest.groovy:23)
当我添加这样的跟踪代码时:
class MyBinding extends Binding {
def getVariable(String name) {
if (! hasVariable(name)) {
BindingTest.defVar(this)
}
println("getVariable: ${name}: ${super.getVariable(name).getClass().getName()}")
return super.getVariable(name)
}
void setVariable (String name, def val) {
println("setVariable: ${name}: ${val.getClass().getName()}")
super.setVariable(name, val)
}
def getProperty(String name) {
println("getProperty: ${name}: ${super.getProperty(name)}")
return super.getProperty(name)
}
void setProperty (String name, def val) {
println("setProperty: ${name}: ${val.getClass().getName()}")
super.setProperty(name, val)
}
}
在可行的情况下,我得到以下输出:
setVariable: test: Test
--- hello ---
在不工作的情况下,我得到以下输出:
setVariable: test: Test
getVariable: test: Test
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
...
两个问题:
getVariable
?Test
返回的getVariable
对象被拒绝了?请注意,此问题特定于可调用的值。如果我将一个简单的值(例如字符串)设置为test
,则两种方法都可以正常工作。例如,进行此类更改:
...
static def defVar(def glob) {
glob.setVariable('test', '--- hello ---')
}
...
shell.parse('println(test)').run()
两种方法的输出都相同:
setVariable: test: java.lang.String
getVariable: test: java.lang.String
setVariable: test: java.lang.String
--- hello ---
但是,我不确定为什么setVariable
被调用两次。我找不到任何说明这些令人困惑行为的文档。这里有人可以给他们一些启发吗?
请注意,所有代码段均经过简化,以便于演示问题,而不是出于预期目的
答案 0 :(得分:2)
将属性用作可调用的后备时,Binding.getVariable()
方法不会涉及。此行为由元类控制,在您的情况下,所有行为都驱动MetaClassImpl.invokePropertyOrMissing()
方法的执行。此方法确定
test()
应调用test.call()
(如果存在现有属性),否则应回退到missingMethod()
方法。这是此方法的实现形式:
private Object invokePropertyOrMissing(Object object, String methodName, Object[] originalArguments, boolean fromInsideClass, boolean isCallToSuper) {
// if no method was found, try to find a closure defined as a field of the class and run it
Object value = null;
final MetaProperty metaProperty = this.getMetaProperty(methodName, false);
if (metaProperty != null)
value = metaProperty.getProperty(object);
else {
if (object instanceof Map)
value = ((Map)object).get(methodName);
}
if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this
Closure closure = (Closure) value;
MetaClass delegateMetaClass = closure.getMetaClass();
return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass);
}
if (object instanceof Script) {
Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
if (bindingVar != null) {
MetaClass bindingVarMC = ((MetaClassRegistryImpl) registry).getMetaClass(bindingVar);
return bindingVarMC.invokeMethod(bindingVar, CLOSURE_CALL_METHOD, originalArguments);
}
}
return invokeMissingMethod(object, methodName, originalArguments, null, isCallToSuper);
}
现在,请注意分支if (object instanceof Script)
以及如何检索绑定变量。它尝试使用以下方法从绑定对象中检索test
变量:
Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
您的代码在以下情况下将起作用:
Object bindingVar = ((Script) object).getBinding().getVariable(methodName);
相反。但事实并非如此。
例如,如果您重写getVariables()
而不是getVariable(String name)
方法,则可以使第二种情况起作用。
class MyBinding extends Binding {
@Override
Map getVariables() {
return super.getVariables() + [
test: new Test()
]
}
}
当然,您的最终实现可能要复杂得多。 (例如,您可以先获取super.getVariables()
映射,检查缺少哪些变量,然后仅在初始映射缺少给定变量的情况下才添加默认变量。)但这取决于您。
或者,考虑使用methodMissing
代替绑定变量后备。它可以使您的代码更易于阅读和推理。