简体   繁体   English

在Java中运行时编译Groovy类

[英]Compile Groovy class at runtime in Java

I am successfully able to compile Groovy in Java at runtime and store it in a database and pull it out. 我成功地能够在运行时在Java中编译Groovy并将其存储在数据库中并将其拉出来。 I can't compile a Groovy class if it has inner classes or an inner enum. 如果它有内部类或内部枚举,我无法编译Groovy类。 Has anyone successfully compiled Groovy code like this and included inner classes/enums and able to pull the script out by classname? 有没有人成功编译这样的Groovy代码并包含内部类/枚举并能够通过类名拉出脚本?

For example, I want to load the "Test" script shown below that contains inner classes and run the script at run time. 例如,我想加载下面显示的包含内部类的“Test”脚本,并在运行时运行脚本。

Compiler code: 编译代码:

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;
}

Code to pull script out: 拉出脚本的代码:

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;
}

Code to run the script: 运行脚本的代码:

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

Groovy script: Groovy脚本:

import com.groovy.groovy.TestScript

class Test implements TestScript {

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

It isn't clear from the description why you are doing the compiling yourself. 从描述中不清楚为什么你自己编译。 If you can just let Groovy do it for you then the whole thing can just be simplified to something like this: 如果你可以让Groovy为你做,那么整个事情可以简化为这样的事情:

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

GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class theParsedClass = groovyClassLoader.parseClass(script);

Ok this may be a little late but hopefully it helps the next person. 好吧这可能有点晚了,但希望它有助于下一个人。 I think you need to save a List for each groovy class and then cl.defineClass and finally cl.loadClass. 我认为你需要为每个groovy类保存一个List,然后是cl.defineClass,最后是cl.loadClass。 I think groovy sometimes compile to a list of classes basically as in below when I addSource(), I add one class and then loop over all the generated classes from that one file. 我认为groovy有时会编译成类的列表,基本上如下所示,当我添加addSource()时,我添加一个类然后循环遍历该文件中的所有生成的类。

This is the code I am currently running(though I have not tried saving and reloading at a later time) 这是我目前正在运行的代码(虽然我以后没有尝试过保存和重新加载)

    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());

take note of the cl.defineClass call which puts the class in the classloader so when it is looked up(the enum or innerclass), it will be there. 注意cl.defineClass调用,它将类放在类加载器中,所以当它被查找时(枚举或内部类),它将在那里。

and so now I think you do not need to create your own class loader(though you avoid useless defineClass until it is needed with your own classloader which can be useful and more performant). 所以现在我认为你不需要创建自己的类加载器(尽管你需要使用自己的类加载器来避免无用的defineClass,这可能是有用且更高效的)。

This forgoes any error handling for the sake of simplicity here, but this is probably what you want: 为了简单起见,这放弃了任何错误处理,但这可能是你想要的:

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;
}

Depending on your requirements, you might want to provide access to the inner classes and you could do that with something like this which finds the class with the matching name instead of assuming the first class: 根据您的要求,您可能希望提供对内部类的访问,您可以使用类似这样的内容来查找具有匹配名称的类,而不是假设第一个类:

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;
}

I am running into this myself but having just done an on-demand java compiler at runtime, I believe you are running into the same issue I solved in this code 我自己也遇到过这个问题,但是在运行时刚刚完成了一个按需java编译器,我相信你遇到了我在这个代码中解决的相同问题

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

webpieces/runtimecompile is a re-usable on-demand java compiler using the eclipse compiler. webpieces / runtimecompile是一个使用eclipse编译器的可重用的按需java编译器。

Now, for groovy, I think you are running into this case 现在,对于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).

If you step through the test case AnonymousByteCacheTest, it pretty much is doing something like that. 如果您单步执行测试用例AnonymousByteCacheTest,它几乎就是在做类似的事情。

you don't need to install ANYTHING to run the build on that project, just clone it and "./gradlew test" and will pass and "./gradlew eclipse" or "./gradlew idea" and it generates IDE files so you can step through it. 你不需要安装ANYTHING来运行该项目的构建,只需克隆它并“./gradlew test”并传递“./gradlew eclipse”或“./gradlew idea”并生成IDE文件,这样你就可以了可以逐步完成它。

It is very very similar. 它非常相似。 I am trying to get the groovy version working next myself. 我试图让groovy版本自己工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM