【发布时间】:2020-05-01 19:38:18
【问题描述】:
我试图通过对象的metaClass 重新定义方法来覆盖 Groovy 自定义 Script 类中定义的方法,但是更改被完全忽略,原始方法按原样调用。我在普通课程上尝试了同样的方法,它按预期工作,所以我想知道在自定义Script 的情况下有什么不同。这是我保存为 `/tmp/t.groovy 的 sn-p:
class Worker {
def init() {
throw new Exception('I always fail')
}
void start() {
init()
println('I never get to work')
}
}
def worker = new Worker()
worker.metaClass.init = { println("dummy init") }
worker.start()
def shell = new GroovyShell()
def script = shell.parse('@groovy.transform.BaseScript TestScript _')
script.metaClass.init = { println("dummy init") }
script.start()
TestScript 类必须在其自己的文件中定义,GroovyShell 才能找到它,所以/tmp/TestScript.groovy 具有类:
abstract class TestScript extends Script {
def init() {
throw new Exception('I always fail')
}
void start() {
init()
println('I never get to work')
}
}
这是我在 Mac OS 上使用 Groovy 2.4.14 运行时的输出:
$ groovy /tmp/t.groovy
dummy init
I never get to work
Caught: java.lang.Exception: I always fail
java.lang.Exception: I always fail
at TestScript.init(TestScript.groovy:3)
at TestScript.start(TestScript.groovy:7)
at TestScript$start.call(Unknown Source)
at t.run(t.groovy:19)
您可以看到对worker.metaClass 所做的更改是有效的,但对script.metaClass 所做的相同更改被忽略了。我尝试先在工作场景中跟踪调用,使用如下内容:
def worker = new Worker()
def m = new DelegatingMetaClass(worker.metaClass) {
Object invokeMethod(Object object, String methodName, Object[] args) {
println("invokeMethod ${methodName}(${args})")
return super.invokeMethod(object, methodName, args)
}
}
m.initialize()
worker.metaClass = m
worker.start()
输出如下:
invokeMethod start([])
invokeMethod init([])
Caught: java.lang.Exception: I always fail
java.lang.Exception: I always fail
...
您可以看到start 和init 都被记录了。当我在脚本对象上尝试相同时:
def shell = new GroovyShell()
def script = shell.parse('@groovy.transform.BaseScript TestScript _')
def m = new DelegatingMetaClass(script.metaClass) {
Object invokeMethod(Object object, String methodName, Object[] args) {
println("invokeMethod ${methodName}(${args})")
return super.invokeMethod(object, methodName, args)
}
}
m.initialize()
script.metaClass = m
script.start()
输出如下:
invokeMethod start([])
Caught: java.lang.Exception: I always fail
java.lang.Exception: I always fail
...
您可以看到对init 的调用永远不会被委托给invokeMethod。我在这里错过了什么?
【问题讨论】:
-
绝对不行。但是为什么需要脚本解析呢?为什么不只是一个类解析?
-
我正在测试作为脚本文件加载的 Jenkins 管道 var 文件。知道为什么脚本可能会违反此处记录的常规 Groovy 元编程模型:groovy-lang.org/metaprogramming.html?
-
你试过这个库来测试 Jenkins 管道 - github.com/jenkinsci/JenkinsPipelineUnit 吗?
-
是的,我已经这样做了,但遗憾的是使用这种方法测试的 vars(可能是 JaCoCo 问题)不会生成任何覆盖率数据,所以我一直在尝试从 var 文件中移动代码的另一种方法到扩展
CpsScript的自定义脚本中,并将其声明为 var 文件中的BaseScript。到目前为止,除了一些奇怪的东西之外,这一直运作良好。 -
@haridsv,实际上如果你这样做
def script = new TestScript(),那么一切正常。因此,通过将 TestScript 作为类加载来解决问题。只有使用@groovy.transform.BaseScript才会出现问题