2014-04-15 13 views
7

Udało mi się skompilować Groovy w Javie w czasie wykonywania i zapisać ją w bazie danych i wyciągnąć. Nie mogę skompilować klasy Groovy, jeśli ma ona wewnętrzne klasy lub wewnętrzne wyliczenie. Czy ktokolwiek z powodzeniem skompilował taki kod Groovy i uwzględnił wewnętrzne klasy/wyliczenia i był w stanie wyciągnąć skrypt przez nazwę klasy?Skompiluj klasę Groovy w środowisku wykonawczym w języku Java

Na przykład chcę załadować poniższy skrypt "Test", który zawiera klasy wewnętrzne i uruchomić skrypt w czasie wykonywania. Kod

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

Kod skryptu ciągnąć się:

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

kod aby uruchomić skrypt:

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

Groovy skrypt:

import com.groovy.groovy.TestScript 

class Test implements TestScript { 

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

Powtarzasz klasy z kompilacjiUnit, ale zwracasz tylko bajty z ostatniej klasy 'compiledScriptBytes = groovyClass.getBytes();' Nie wiem, czy tak jest, ale wygląda na potencjalny błąd. – airborn

+0

Próbowałem iterować we wszystkich klasach i przechowywać je w jednym bajcie [], ale to nie zadziałało, gdy otrzymałem klasę groovy i przerzuciłem ją do mojego interfejsu Java. – ColinMc

Odpowiedz

5

nie jest jasne, z opisu dlaczego robisz kompilowania siebie. Jeśli możesz po prostu pozwolić Groovy zrobi to za ciebie wtedy cała sprawa może być tylko uproszczone do czegoś takiego:

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

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

Wspomniałem, że chciałem skompilować skrypty Groovy i umieścić je w bazie danych. Powodem tego jest wydajność, więc gdy chcę uruchomić skrypt, nie muszę go kompilować wiele razy. Skrypty uruchamiane są co minutę, a kompilowanie skryptu co minutę wydaje się nieefektywne. – ColinMc

+0

Gdybym nie musiał kompilować skryptów i przechowywać ich, to by działało. – ColinMc

+0

Jeśli każda minuta pobierania bajtów z bazy danych, a następnie wywołanie metody defineClass() w celu przekształcenia tych bajtów w klasę, prawdopodobnie można to poprawić. Czy twoja aplikacja jest taka, że ​​jest jakiś powód, dla którego nie możesz uzyskać dostępu do programu ładującego klasy, który jest dostępny za każdym razem, gdy potrzebujesz dostępu do klasy skryptów? Tak długo, jak możesz to zrobić, możesz uniknąć konieczności używania funkcji defineClass(), aby zamienić bajty w klasę za każdym razem, gdy trzeba ją uruchomić. Można to zrobić tylko raz, a następnie za każdym razem, gdy trzeba uruchomić ten skrypt, klasa będzie już dostępna w programie ładującym klasy. –

0

ten rezygnuje żadnego błędu obsługi dla uproszczenia tutaj, ale to jest chyba to, co chcesz:

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

W zależności od potrzeb, może chcesz, aby zapewnić dostęp do wewnętrznego klas i można zrobić z coś takiego który wyszukuje klasę o nazwie pasującej zamiast zakładając pierwszą klasę:

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

Używam do tego siebie, ale po prostu zrobić kompilator Javy na żądanie w czasie wykonywania, I wierzę, że są uruchomione w tym samym numerze Rozwiązałem w tym kodzie

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

webpieces/runtimecompile jest wielokrotnego użytku na żądanie kompilator Javy za pomocą kompilatora eclipse.

Teraz, Groovy, myślę, że są uruchomione w tym przypadku

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

Jeśli prześledzić przypadku testowego AnonymousByteCacheTest, to dość dużo robi coś takiego.

Nie musisz instalować nic, aby uruchomić kompilację tego projektu, po prostu sklonuj go i "./gradlew test" i przejdzie i "./gradlew eclipse" lub "pomysł ./gradlew" i wygeneruje Pliki IDE, dzięki którym można przez nie przejść.

Jest bardzo podobny. Staram się, aby ta fajna wersja działała dalej.

3

OK, może to trochę za późno, ale mam nadzieję, że pomoże to następnej osobie. Myślę, że musisz zapisać listę dla każdej klasy groovy, a następnie cl.defineClass i na końcu cl.loadClass. Myślę, że czasami groovy kompiluje się do listy klas zasadniczo jak poniżej, kiedy dodamSource(), dodaję jedną klasę, a następnie zapętrzę na wszystkie wygenerowane klasy z tego jednego pliku.

Jest to kod Ja obecnie pracuje (chociaż nie próbowałem oszczędności i przeładunku w późniejszym czasie)

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

zwróć uwagę na wezwanie cl.defineClass co stawia klasę w classloader więc kiedy jest podniecony (enum lub klasa wewnętrzna), będzie tam.

i tak teraz myślę, że nie trzeba tworzyć własnego programu ładującego klasy (choć unikasz bezużytecznego defineClass, dopóki nie będzie potrzebny z własnym programem ładującym klasy, który może być przydatny i bardziej wydajny).

Powiązane problemy