2014-04-15 5 views
7

Ich bin in der Lage, Groovy in Java zur Laufzeit zu kompilieren und in einer Datenbank zu speichern und herauszuziehen. Ich kann eine Groovy-Klasse nicht kompilieren, wenn sie innere Klassen oder eine innere Enum hat. Hat jemand erfolgreich Groovy-Code wie diesen kompiliert und interne Klassen/Enums enthalten und das Skript nach Klassennamen ziehen können?Kompiliere Groovy-Klasse zur Laufzeit in Java

Zum Beispiel möchte ich das "Test" -Skript laden, das innere Klassen enthält und das Skript zur Laufzeit ausführen.

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 ziehen Skript aus:

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, um das Skript auszuführen:

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

Groovy Skript:

import com.groovy.groovy.TestScript 

class Test implements TestScript { 

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

Sie iterieren über Klassen von compilationUnit, aber Sie geben nur Bytes aus der letzten Klasse 'compiledScriptBytes = groovyClass.getBytes();' Ich weiß nicht, ob das der Fall ist, aber das sieht wie ein potenzieller Fehler aus. – airborn

+0

Nun, ich habe versucht, über alle Klassen zu iterieren und sie in einem Byte zu speichern [], aber das hat nicht funktioniert, als ich die Groovy-Klasse bekommen und sie auf meine Java-Schnittstelle übertragen habe. – ColinMc

Antwort

5

Es ist nicht klar aus der Beschreibung, warum machst du das Kompilieren selbst. Wenn Sie können lassen Groovy es für Sie tun dann kann das Ganze nur so zu etwas vereinfacht werden:

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

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

Ich erwähnte, dass ich die Groovy-Skripte kompilieren und in die Datenbank einfügen wollte. Der Grund dafür ist die Performance. Wenn ich also ein Skript ausführen möchte, muss ich es nicht mehrmals kompilieren. Die Skripte werden jede Minute ausgeführt und das Erstellen eines Skripts erscheint jede Minute als ineffizient. – ColinMc

+0

Wenn ich die Skripte nicht kompilieren und speichern müsste, würde das funktionieren. – ColinMc

+0

Wenn Sie jede Minute Bytes aus einer Datenbank abrufen und dann defineClass() aufrufen, um diese Bytes in eine Klasse umzuwandeln, dann können Sie das wahrscheinlich verbessern. Ist Ihre Anwendung so beschaffen, dass Sie aus irgendeinem Grund nicht auf einen Klassenlader zugreifen können, der jedes Mal verfügbar ist, wenn Sie auf die Skriptklasse zugreifen müssen? Solange Sie dies tun können, können Sie vermeiden, dass Sie die defineClass() - Funktion ausführen müssen, um Ihre Bytes jedes Mal in eine Klasse umzuwandeln, wenn Sie sie ausführen müssen. Sie können dies nur einmal tun, und jedes Mal, wenn Sie dieses Skript ausführen müssen, ist die Klasse bereits im Klassenlader verfügbar. –

0

Diese verzichtet auf einen Fehler aus Gründen der Einfachheit der Handhabung hier, aber das ist wahrscheinlich das, was Sie wollen:

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

Je nach Bedarf könnten Sie Zugang zu den inneren Klassen zur Verfügung stellen möchten und Sie tun können, dass mit so etwas wie dieses, die die Klasse mit dem passenden Namen statt unter der Annahme der ersten Klasse findet:

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

Ich bin in diese läuft mir aber haben gerade einen On-Demand-Java-Compiler zur Laufzeit getan, ich glauben Sie werden in das gleiche Problem laufen ich in diesem Code gelöst

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

webpieces/runtimecompile ist ein wiederverwendbarer on-Demand-Java-Compiler die eclipse-Compiler.

nun für stark, ich glaube, Sie in diesem Fall laufen

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). 

Wenn Sie den Testfall für Schritt durch AnonymousByteCacheTest, es ist so ziemlich so etwas wie das zu tun.

Sie müssen nichts installieren, um den Build auf diesem Projekt zu starten, klonen Sie einfach und "./gradlew test" und wird übergeben und "./gradlew Eclipse" oder "./gradlew Idee" und es generiert IDE-Dateien, damit Sie Schritt für Schritt durchgehen können.

Es ist sehr, sehr ähnlich. Ich versuche, die groovige Version als nächstes selbst zu arbeiten.

3

Ok, das könnte etwas spät sein, aber hoffentlich hilft es der nächsten Person. Ich denke, du musst eine Liste für jede groovige Klasse und dann cl.defineClass und schließlich cl.loadClass speichern. Ich denke groovy manchmal zu einer Liste von Klassen im Grunde wie unten kompilieren, wenn ich addSource(), füge ich eine Klasse und dann Schleife über alle generierten Klassen aus dieser einen Datei.

Dies ist der Code, den ich zur Zeit laufen bin (obwohl ich nicht versucht habe, speichern und zu einem späteren Zeitpunkt neu zu laden)

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

nehmen den cl.defineClass Ruf, der so die Klasse im Klassenlader stellt, wenn es wird nachgeschlagen (enum oder innerclass), es wird da sein.

und so jetzt denke ich, dass Sie nicht Ihren eigenen Klassenlader erstellen müssen (obwohl Sie unnützes defineClass vermeiden, bis es mit Ihrem eigenen Klassenlader benötigt wird, der nützlich und leistungsfähiger sein kann).