GroovyClassLoader 对 parseClass 的调用是成功的,即使代码没有编译

GroovyClassLoader call to parseClass is successful, even when code does not compile

我正在尝试将 Groovy 脚本作为 class 动态加载,但即使脚本代码未编译,也会创建 class 对象。

例如,我的Groovy加载Groovy脚本的代码简化版如下:

GroovyCodeSource src = new GroovyCodeSource(
    "blah blah blah",
    "Foo.groovy",
    GroovyShell.DEFAULT_CODE_BASE
)
new GroovyClassLoader().parseClass(src, true)

显然,代码 blah blah blah 不是合法的 Groovy 脚本。然而,为这个动态代码成功创建了一个 class 对象。根据 GroovyClassLoader's Javadoc for the parseClass method,对于这种情况,应该抛出 CompilationFailedException

如何仍然为损坏的代码创建 class 以及如何根据代码是否有条件地从动态 Groovy 源代码成功创建 class会编译吗?我做了很多研究和实验,但无济于事。

这是因为 groovy 提供了对方法和属性的动态访问,并且就 Groovy 而言,代码 blah blah blah 是有效的。实际上,您正在为脚本提供代码(没有 class 声明)。编译后,你会得到一个 class extends groovy.lang.Script.

那么,让我继续您的代码并向您展示它如何有效...

GroovyCodeSource src = new GroovyCodeSource(
    'blah blah blah',
    "Foo.groovy",
    GroovyShell.DEFAULT_CODE_BASE
)
def c = new GroovyClassLoader().parseClass(src, true)
println c                     //class Foo
println c.getSuperclass()     //class groovy.lang.Script

def i = c.newInstance()
//i.run()                     //MissingPropertyException: No such property: blah for class: Foo
i.setBinding([
    blah: { x-> return [blah: "x.class =${x.getClass()}"] }
] as Binding)
i.run()                       //SUCCESS

我也建议你运行groovyconsole,输入blah blah blah,按Ctrl+T,然后检查什么class 是为您的脚本生成的。请注意,您可以在不同的 compilation/parsing 阶段之间切换。


一个可能的解决方法是在方法上使用 CompileStatic 注释或 class:

//compilation of this code will fail with message
//[Static type checking] - The variable [blah] is undeclared.
@groovy.transform.CompileStatic
def f(){
    blah blah blah
}
f()

您可以强制 GroovyClassLoader 对整个脚本进行静态验证。

假设您希望您的脚本仅访问一些预定义的 variables/methods 并且您希望在编译步骤而不是 运行 时检查它。

以下示例显示了如何执行此操作,它会在编译期间使 blah blah blah 代码失败:

import org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder
import org.codehaus.groovy.control.CompilerConfiguration
import groovy.transform.CompileStatic

//your base Script class that declares only valid members
//for example `log`
abstract class MyScript extends groovy.lang.Script{
    PrintStream log
}

//create compiler config with base script class 
CompilerConfiguration cc = new CompilerConfiguration()
cc.setScriptBaseClass(MyScript.class.getName())
//make static compilation set for class loader
cc = CompilerCustomizationBuilder.withConfig(cc){ 
    ast(CompileStatic) 
}
//create classloader with compile config
GroovyClassLoader gcl = new GroovyClassLoader(this.getClass().getClassLoader(),cc)


GroovyCodeSource src = new GroovyCodeSource(
    "log.println 'hello world'",
    "Foo.groovy",
    GroovyShell.DEFAULT_CODE_BASE
)
def c = gcl.parseClass(src, true)  //this will fail for 'blah blah blah' source
def i = c.newInstance(log:System.out)
i.run()

P.S。 Groovy.

中还有其他代码转换器可用