【问题标题】:GroovyClassLoader call to parseClass is successful, even when code does not compile对 parseClass 的 GroovyClassLoader 调用成功,即使代码无法编译
【发布时间】:2020-01-13 19:05:11
【问题描述】:

我正在尝试将 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's Javadoc for the parseClass method CompilationFailedException 应该抛出这样的情况。

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

【问题讨论】:

    标签: java groovy classloader groovyclassloader


    【解决方案1】:

    这是因为 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()
    

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

    【讨论】:

    • 为整个脚本示例添加了编译静态
    • 这是一个很好的答案!非常感谢您的详细教育。对我来说,救命稻草是CompileStatic AST 变压器。
    猜你喜欢
    • 1970-01-01
    • 2011-08-31
    • 1970-01-01
    • 2018-03-29
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    相关资源
    最近更新 更多