2014-04-15 5 views
7

Sono riuscito a compilare correttamente Groovy in Java in fase di esecuzione e archiviarlo in un database ed estrarlo. Non riesco a compilare una classe Groovy se ha classi interne o enum interiori. Qualcuno ha compilato con successo il codice Groovy in questo modo e ha incluso classi interne/enumerazione ed è in grado di estrarre lo script in base al nome della classe?Compilazione classe Groovy in fase di esecuzione in Java

Ad esempio, voglio caricare lo script "Test" mostrato di seguito che contiene le classi interne ed eseguire lo script in fase di esecuzione. Codice

Compiler:

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

codice per tirare fuori copione:

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

codice per eseguire lo script:

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

lo script Groovy:

import com.groovy.groovy.TestScript 

class Test implements TestScript { 

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

di eseguire iterazioni su classi da compilationUnit, ma Tu ritorno byte solo l'ultima classe 'compiledScriptBytes = groovyClass.getBytes();' Non so se questo è il caso, ma questo sembra un potenziale bug. – airborn

+0

Beh, ho provato a ripetere tutte le classi e memorizzarle in un byte [] ma non ha funzionato quando ho ottenuto la classe groovy e l'ho lanciata nell'interfaccia Java. – ColinMc

risposta

5

non è chiaro dalla descrizione perché si sta facendo la compilazione da soli. Se si può semplicemente lasciare Groovy farlo per voi, allora il tutto può solo essere semplificata a qualcosa di simile:

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

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

Ho detto che volevo compilare gli script di Groovy e inserirli nel database. La ragione di questo è per le prestazioni, quindi quando voglio eseguire uno script non devo compilarlo più volte. Gli script vengono eseguiti ogni minuto e la compilazione di uno script ogni minuto sembra inefficiente. – ColinMc

+0

Se non dovessi compilare gli script e archiviarli, questo funzionerebbe. – ColinMc

+0

Se ogni minuto recupera byte da un database e poi chiama defineClass() per trasformare quei byte in una classe, puoi probabilmente migliorarlo. La tua applicazione è tale che non è possibile accedere ad un programma di caricamento classi che è disponibile ogni volta che è necessario accedere alla classe di script? Finché puoi farlo, puoi evitare di dover fare la cosa defineClass() per trasformare i tuoi byte in una classe ogni volta che devi eseguirla. Potresti farlo solo una volta e ogni volta che devi eseguire lo script, la classe sarebbe già disponibile nel caricatore di classi. –

0

Questa rinuncia ad ogni trattamento per semplicità qui l'errore, ma questo è probabilmente quello che si vuole:

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

A seconda delle esigenze, si potrebbe desiderare di fornire l'accesso alle classi interne e si potrebbe farlo con qualcosa di simile che trova la classe con il nome corrispondente invece di assumere la prima classe:

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

Sono in esecuzione in questo io stesso, ma avendo appena fatto un compilatore java on-demand in fase di esecuzione, I credi si esegue lo stesso problema ho risolto in questo codice

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

webpieces/runtimecompile è un riutilizzabile compilatore Java on-demand utilizzando il compilatore Eclipse.

Ora, per groove, penso che si esegue in questo caso

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

Se fate un passo attraverso il banco di prova AnonymousByteCacheTest, che più o meno sta facendo qualcosa di simile.

non è necessario installare NULLA per eseguire la generazione su quel progetto, basta clonarlo e "./gradlew test" e passerà e "./gradlew eclipse" o "./gradlew idea" e genera File IDE in modo da poterlo attraversare.

È molto simile. Sto cercando di ottenere la versione groovy lavorando accanto a me stesso.

3

Ok, potrebbe essere un po 'tardi, ma si spera che aiuti la persona successiva. Penso che sia necessario salvare una lista per ogni classe groovy e poi cl.defineClass e infine cl.loadClass. Penso che Groovy a volte compili un elenco di classi fondamentalmente come in seguito quando aggiungoSource(), aggiungo una classe e quindi esegui il loop su tutte le classi generate da quel file.

Questo è il codice Sono attualmente in esecuzione (anche se non ho provato salvare e ricaricare in un secondo momento)

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

prendere nota della chiamata cl.defineClass che mette la classe nel classloader così quando viene guardato in su (enum o innerclass), sarà lì.

e così ora penso che non sia necessario creare il proprio caricatore di classi (anche se si evita defineClass inutile finché non è necessario con il proprio classloader che può essere utile e più performante).