6

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

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

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

显然,该代码blah blah blah不是合法的 Groovy 脚本。然而,为这个动态代码成功地创建了一个类对象。根据GroovyClassLoader 的 Javadoc,对于parseClassCompilationFailedException这种情况,应该抛出方法a 。

怎么可能仍然为损坏的代码创建该类,以及如何根据代码是否编译成功地从动态 Groovy 源代码创建一个类?我做了很多研究和实验,但无济于事。

4

1 回答 1

6

这是因为 groovy 提供了对方法和属性的动态访问,并且就 Groovy 而言,代码blah blah blah是有效的。实际上,您正在为 Script 提供代码(没有类声明)。编译后,你会得到一个扩展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并检查为您的脚本生成的类。请注意,您可以在不同的编译/解析阶段之间切换。 在此处输入图像描述


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

//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对整个脚本进行静态验证。

假设您希望您的脚本仅访问一些预定义的变量/方法,并且您希望在编译步骤而不是在运行时检查这一点。

以下示例显示了如何执行此操作,它将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()

PS Groovy 中还有其他代码转换器。

于 2019-09-12T10:14:22.653 回答