在 Groovy 中,使用元类替换接口中定义的方法已被破坏。在这种情况下,exec
方法定义在Project
类,它是一个接口。从Groovy-3493(最初报道于 2009 年):
"Cannot override methods via metaclass that are part of an interface implementation"
解决方法
invokeMethod
拦截所有方法并且可以工作。这有点矫枉过正,但确实有效。当方法名匹配时exec
,它将呼叫转移到mySpecialInstance
目的。否则它会传递给委托,即现有方法。谢谢调用方法委托 and 记录所有方法对此的输入。
// This intercepts all methods, stubbing out exec and passing through all other invokes
this.project.metaClass.invokeMethod = { String name, args ->
if (name == 'exec') {
// Call special instance to track verifications
mySpecialInstance.exec((Closure) args.first())
} else {
// This calls the delegate without causing infinite recursion
MetaMethod metaMethod = delegate.class.metaClass.getMetaMethod(name, args)
return metaMethod?.invoke(delegate, args)
}
}
除了您可能会看到有关“参数数量错误”或“无法在空对象上调用方法 xxxxx”的异常之外,这种方法效果很好。问题是上面的代码不处理方法参数的强制。为了project.files(Object... paths)
,invokeMethod 的参数应采用以下形式[['path1', 'path2']]
。但是,在某些情况下,需要调用files(null)
or files()
所以 invokeMethod 的参数是[null]
and []
分别按预期失败[[]]
。产生上述错误。
以下代码仅解决了这个问题files
方法但这对于我的单元测试来说已经足够了。我仍然想找到一种更好的方法来强制类型或理想情况下替换单个方法。
// As above but handle coercing of the files parameter types
this.project.metaClass.invokeMethod = { String name, args ->
if (name == 'exec') {
// Call special instance to track verifications
mySpecialInstance.exec((Closure) args.first())
} else {
// This calls the delegate without causing infinite recursion
// https://stackoverflow.com/a/10126006/1509221
MetaMethod metaMethod = delegate.class.metaClass.getMetaMethod(name, args)
logInvokeMethod(name, args, metaMethod)
// Special case 'files' method which can throw exceptions
if (name == 'files') {
// Coerce the arguments to match the signature of Project.files(Object... paths)
// TODO: is there a way to do this automatically, e.g. coerceArgumentsToClasses?
assert 0 == args.size() || 1 == args.size()
if (args.size() == 0 || // files()
args.first() == null) { // files(null)
return metaMethod?.invoke(delegate, [[] as Object[]] as Object[])
} else {
// files(ArrayList) possibly, so cast ArrayList to Object[]
return metaMethod?.invoke(delegate, [(Object[]) args.first()] as Object[])
}
} else {
// Normal pass through
return metaMethod?.invoke(delegate, args)
}
}
}