我的目标是改变Groovy中方法授权的顺序,以便能够动态地覆盖Groovy对象中的方法。
我有一个名为 Weapon
定义如下。
class Weapon {
Prefix prefix
String method() {
'Called from Weapon'
}
}
其中 prefix
是这个类的一个实例。
class Prefix {
final Map<String, Closure> methods = [:]
void propertyMissing(String name, Closure value) {
if (value != null) {
methods[name] = value
}
else {
methods.remove(name)
}
}
def methodMissing(String name, args) {
if (!methods.containsKey(name)) {
throw new MissingMethodException(name, Prefix, args)
}
methods.(name)(args)
}
}
这种设计允许 Prefix
以在运行时动态添加方法。
在运行时动态添加方法。特定 想要的行为是,任何由 Weapon
将首先在 prefix
(如果它不是空的)。如果它不存在,那么 Weapon
将使用其正常行为搜索该方法(如这里所示。https:/docs.groovy-lang.orglatesthtmldocumentationcore-metaprogramming.html#_runtime_metaprogramming。).
下面是一个简单的例子,说明所需的行为。
Weapon w = new Weapon()
Prefix p = new Prefix()
p.method = { 'Called from Prefix' }
w.prefix = p
assert w.method() == 'Called from Prefix'
w.prefix = null
assert w.method() == 'Called from Weapon'
我对Groovy的元编程相当陌生,所以我并不了解它的所有功能。起初,我以为可以通过简单地覆盖掉 invokeMethod
中的方法,但Groovy最终还是在找到这个方法之前,找到了 invokeMethod
被调用过(我假设它是在元类或类中找到的)。
我能想到的唯一的解决方案是创建一个自定义的元类,用于 Weapon
这将首先检查 Weapon
的前缀。我的实现在下面。
class WeaponMetaClass {
WeaponMetaClass(MetaClass metaClass) {
super(metaClass)
}
Object invokeMethod(Object object, String methodName, Object[] args) {
if (object instanceof Weapon && object.prefix != null) {
try {
return object.prefix.(methodName)(args)
}
catch (MissingMethodException ignored) {
}
}
return super.invokeMethod(object, methodName, args)
}
}
然而,这并不奏效,每一个方法的调用都是来自于 Weapon
(有或没有 prefix
)返回的是null。我的实现是否出错了,有没有更好的方法,或者根本不可能做到这一点?
你可以使用默认的metaClass来实现所需的行为。
class Weapon {
void setPrefix(Map m){
this.metaClass=null //reset to default class behavior
if(m)m.each{k,v-> this.metaClass[k]=v} //redefine methods/properties from map
}
String method() {
'Called from Weapon'
}
}
def w = new Weapon()
//you could wrap prefix with custom class - however it's just a map container
def prefix=[
method: {'Called from Prefix'}
]
assert w.method()=='Called from Weapon'
w.prefix = prefix
assert w.method()=='Called from Prefix'
w.prefix = null
assert w.method()=='Called from Weapon'