9

我能够在运行时成功地在 Java 中编译 Groovy 并将其存储在数据库中并将其提取出来。如果 Groovy 类具有内部类或内部枚举,我将无法编译它。有没有人成功编译过这样的 Groovy 代码并包含内部类/枚举并能够通过类名提取脚本?

例如,我想加载下面显示的包含内部类的“测试”脚本并在运行时运行该脚本。

编译器代码:

public byte[] compileGroovyScript(final String className, final String script) {
    byte[] compiledScriptBytes = null;
    CompilationUnit compileUnit = new CompilationUnit();
    compileUnit.addSource(className, script);
    compileUnit.compile(Phases.CLASS_GENERATION);

    for (Object compileClass : compileUnit.getClasses()) {
        GroovyClass groovyClass = (GroovyClass) compileClass;
        compiledScriptBytes = groovyClass.getBytes();
    }

    return compiledScriptBytes;
}

提取脚本的代码:

public Class getGroovyScript(final String className, final byte[] script) {
    Class clazz = null;

    try (GroovyClassLoader classLoader = new GroovyClassLoader(this.getClass().getClassLoader())) {
        clazz = classLoader.defineClass(className, script);
    } catch (IOException e) {
    } catch (Exception e) {
    }

    return clazz;
}

运行脚本的代码:

Class groovyClass = app.getGroovyScript(className, compiledScript);
TestScript script = (TestScript) groovyClass.newInstance();
System.out.println(script.getMessage());

Groovy 脚本:

import com.groovy.groovy.TestScript

class Test implements TestScript {

    String getMessage() {
        [1..10].each(){
            println it
        }
        return "Jello"
    }
}
4

5 回答 5

13

从描述中不清楚你为什么要自己编译。如果你可以让 Groovy 为你做这件事,那么整个事情就可以简化为这样的事情:

String script = // string containing the script you want to parse

GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class theParsedClass = groovyClassLoader.parseClass(script);
于 2014-04-16T21:32:52.550 回答
5

好的,这可能有点晚了,但希望它可以帮助下一个人。我认为您需要为每个 groovy 类保存一个列表,然后是 cl.defineClass,最后是 cl.loadClass。我认为当我 addSource() 时,groovy 有时会编译成一个类列表,基本上如下所示,我添加一个类,然后遍历该文件中所有生成的类。

这是我目前正在运行的代码(虽然我没有尝试过保存和稍后重新加载)

    GroovyClassLoader cl = new GroovyClassLoader();
    CompilationUnit compileUnit = new CompilationUnit();
    compileUnit.addSource(scriptCode.getClassName(), scriptCode.getScriptSourceCode());
    compileUnit.compile(Phases.CLASS_GENERATION);
    compileUnit.setClassLoader(cl);

    GroovyClass target = null;
    for (Object compileClass : compileUnit.getClasses()) {
        GroovyClass groovyClass = (GroovyClass) compileClass;
        cl.defineClass(groovyClass.getName(), groovyClass.getBytes());
        if(groovyClass.getName().equals(scriptCode.getClassName())) {
            target = groovyClass;
        }
    }

    if(target == null) 
        throw new IllegalStateException("Could not find proper class");

    return cl.loadClass(target.getName());

注意将类放入类加载器中的 cl.defineClass 调用,因此当查找它(枚举或内部类)时,它将在那里。

所以现在我认为您不需要创建自己的类加载器(尽管您避免使用无用的 defineClass 直到您自己的类加载器需要它,这可能有用且性能更高)。

于 2016-07-02T00:10:04.397 回答
0

为了简单起见,这放弃了任何错误处理,但这可能是您想要的:

public byte[] compileGroovyScript(final String className, final String script) {
    byte[] compiledScriptBytes = null;
    CompilationUnit compileUnit = new CompilationUnit();
    compileUnit.addSource(className, script);
    compileUnit.compile(Phases.CLASS_GENERATION);

    List classes = compileUnit.getClasses();
    GroovyClass firstClass = (GroovyClass)classes.get(0);
    compiledScriptBytes = firstClass.getBytes();

    return compiledScriptBytes;
}
于 2014-04-16T21:19:33.970 回答
0

根据您的要求,您可能希望提供对内部类的访问,您可以使用类似这样的方法来执行此操作,它会找到具有匹配名称的类,而不是假设第一个类:

public byte[] compileGroovyScript(final String className, final String script) {
    byte[] compiledScriptBytes = null;
    CompilationUnit compileUnit = new CompilationUnit();
    compileUnit.addSource(className, script);
    compileUnit.compile(Phases.CLASS_GENERATION);

    for (Object compileClass : compileUnit.getClasses()) {
        GroovyClass groovyClass = (GroovyClass) compileClass;
        if(className.equals(groovyClass.getName())) {
            compiledScriptBytes = groovyClass.getBytes();
            break;
         }

    }

    return compiledScriptBytes;
}
于 2014-04-16T21:29:05.403 回答
0

我自己也遇到了这个问题,但是刚刚在运行时完成了一个按需 java 编译器,我相信你遇到了我在这段代码中解决的同样的问题

https://github.com/deanhiller/webpieces/tree/master/runtimecompile/src/main/java/org/webpieces/compiler/api

webpieces/runtimecompile 是一个使用 eclipse 编译器的可重用的按需 java 编译器。

现在,对于 groovy,我认为您遇到了这种情况

1. you compile ONE script
2. this results in 'multiple' class file objects (I think) just like mine did
3. This is where you need to store EACH in the database SEPARATELY
4. Then you need a classloader that tries to lookup the 'inner classes' when jvm asks for it
5. finally you do a yourclassLoader.loadApplicationClass (much like the one in CompileOnDemandImpl.java in the project above
6. To be clear, step 5 causes step 4 to happen behind the scenes (and that is what is confusing).

如果您逐步完成测试用例 AnonymousByteCacheTest,它几乎就是在做类似的事情。

您无需安装任何东西即可在该项目上运行构建,只需克隆它和“./gradlew test”并通过“./gradlew eclipse”或“./gradlew idea”它会生成IDE文件,所以你可以通过它。

它非常相似。我正在尝试让 groovy 版本自己运行。

于 2016-07-01T23:07:10.983 回答