2014-09-01 11 views
8

Próbuję pobrać opis kilku Java Beans z pliku XML. Chciałbym dodać do nich adnotacje z @Data z projektu lombok, aby automatycznie włączyć konstruktor, równe, hashCode, getters, setters i toString. Chciałbym skompilować je w pamięci, wygenerować kilka instancji (z danymi z tego samego pliku XML) i dodać je do Droolsa, aby w końcu zrobić pewne rozumowanie na temat tych danych.Kompilowanie klasy Java w pamięci za pomocą adnotacji `lombok` i Java JDK 8

Niestety, nie mogę skompilować tych lekcji, dlatego proszę o pomoc!

Poniższy kod pokazuje jak programowo kompilacji klas Java w pamięci:

package example; 

import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List; 

import javax.tools.JavaCompiler; 
import javax.tools.JavaFileManager; 
import javax.tools.JavaFileObject; 
import javax.tools.ToolProvider; 

public class Simple { 

    public static void main(String[] args) throws Exception { 
     String name = "Person"; 
     String content = // 
      "public class " + name + " {\n" + // 
      " @Override\n" + // 
      " public String toString() {\n" + // 
      "  return \"Hello, world!\";\n" + // 
      " }\n" + // 
      "}\n"; 
     System.out.println(content); 

     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
     JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null)); 

     List<String> options = new ArrayList<String>(); 
     options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path"))); 

     List<JavaFileObject> files = new ArrayList<JavaFileObject>(); 
     files.add(new MemoryJavaFileObject(name, content)); 

     compiler.getTask(null, manager, null, options, null, files).call(); 

     Object instance = manager.getClassLoader(null).loadClass(name).newInstance(); 
     System.out.println(instance); 
    } 

} 

gdzie MemoryFileManager jest:

package example; 

import java.io.IOException; 
import java.security.SecureClassLoader; 

import javax.tools.FileObject; 
import javax.tools.ForwardingJavaFileManager; 
import javax.tools.JavaFileObject; 
import javax.tools.JavaFileObject.Kind; 
import javax.tools.StandardJavaFileManager; 

public class MemoryFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> { 

    private MemoryJavaClassObject object; 

    public MemoryFileManager(StandardJavaFileManager manager) { 
     super(manager); 
    } 

    @Override 
    public ClassLoader getClassLoader(Location location) { 
     return new SecureClassLoader() { 
      @Override 
      protected Class<?> findClass(String name) throws ClassNotFoundException { 
       byte[] b = object.getBytes(); 
       return super.defineClass(name, object.getBytes(), 0, b.length); 
      } 
     }; 
    } 

    @Override 
    public JavaFileObject getJavaFileForOutput(Location location, String name, Kind kind, FileObject sibling) throws IOException { 
     object = new MemoryJavaClassObject(name, kind); 
     return object; 
    } 

} 

i MemoryJavaClassObject jest:

package example; 

import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.OutputStream; 
import java.net.URI; 

import javax.tools.SimpleJavaFileObject; 

public class MemoryJavaClassObject extends SimpleJavaFileObject { 

    protected final ByteArrayOutputStream stream = new ByteArrayOutputStream(); 

    public MemoryJavaClassObject(String name, Kind kind) { 
     super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); 
    } 

    public byte[] getBytes() { 
     return stream.toByteArray(); 
    } 

    @Override 
    public OutputStream openOutputStream() throws IOException { 
     return stream; 
    } 

} 

i wreszcie MemoryJavaFileObject jest :

package example; 

import java.net.URI; 

import javax.tools.SimpleJavaFileObject; 

public class MemoryJavaFileObject extends SimpleJavaFileObject { 

    private CharSequence content; 

    protected MemoryJavaFileObject(String className, CharSequence content) { 
     super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 
     this.content = content; 
    } 

    @Override 
    public CharSequence getCharContent(boolean ignoreEncodingErrors) { 
     return content; 
    } 

} 

Gdybym uruchomić przykład w pierwszym bloku kodu, pojawia się następujący komunikat, zgodnie z oczekiwaniami:

public class Person { 
    @Override 
    public String toString() { 
     return "Hello, world!"; 
    } 
} 

Hello, world! 

Teraz, jeśli dodać lombok.jar do mojego projektu i obejmują następujący przykład :

package example; 

import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List; 

import javax.tools.JavaCompiler; 
import javax.tools.JavaFileManager; 
import javax.tools.JavaFileObject; 
import javax.tools.ToolProvider; 

public class Lombok { 

    public static void main(String[] args) throws Exception { 
     String name = "Person"; 
     String content = // 
      "import lombok.Data;\n" + // 
      "public @Data class " + name + " {\n" + // 
      " private String name;\n" + // 
      "}\n"; 
     System.out.println(content); 

     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
     JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null)); 

     List<String> options = new ArrayList<String>(); 
     options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path"))); 

     List<JavaFileObject> files = new ArrayList<JavaFileObject>(); 
     files.add(new MemoryJavaFileObject(name, content)); 

     compiler.getTask(null, manager, null, options, null, files).call(); 

     Object instance = manager.getClassLoader(null).loadClass(name).newInstance(); 
     System.out.println(instance); 
    } 

} 

niestety nie uzyskać spodziewanych rezultatów, ale raczej:

import lombok.Data; 
public @Data class Person { 
    private String name; 
} 

/Person.java:2: warning: Can't initialize javac processor due to (most likely) a class loader  problem: java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment 
public @Data class Person { 
      ^
     at lombok.javac.apt.Processor.init(Processor.java:84) 
     at lombok.core.AnnotationProcessor$JavacDescriptor.want(AnnotationProcessor.java:87) 
     at lombok.core.AnnotationProcessor.init(AnnotationProcessor.java:141) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.<init>(JavacProcessingEnvironment.java:500) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors$ProcessorStateIterator.next(JavacProcessingEnvironment.java:597) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:690) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1035) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1176) 
     at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1173) 
     at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:859) 
     at com.sun.tools.javac.main.Main.compile(Main.java:523) 
     at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129) 
     at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138) 
     at example.Lombok.main(Lombok.java:42) 
    Caused by: java.lang.ClassNotFoundException: com.sun.tools.javac.processing.JavacProcessingEnvironment 
     at java.net.URLClassLoader$1.run(URLClassLoader.java:372) 
     at java.net.URLClassLoader$1.run(URLClassLoader.java:361) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at java.net.URLClassLoader.findClass(URLClassLoader.java:360) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:424) 
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:357) 
     ... 15 more 
1 warning 
[email protected] 

Należy zauważyć, że klasa zostaje skompilowana, a domyślna metoda toString() jest wykonywana, ponieważ wyświetlane są typowe wyniki. zauważyć również, że jeśli uruchomię byłego przykład, teraz mam następujące:

public class Person { 
    @Override 
    public String toString() { 
     return "Hello, world!"; 
    } 
} 

/Person.java:1: warning: Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment 
public class Person { 
    ^
     at lombok.javac.apt.Processor.init(Processor.java:84) 
     at lombok.core.AnnotationProcessor$JavacDescriptor.want(AnnotationProcessor.java:87) 
     at lombok.core.AnnotationProcessor.init(AnnotationProcessor.java:141) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.<init>(JavacProcessingEnvironment.java:500) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors$ProcessorStateIterator.next(JavacProcessingEnvironment.java:597) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:690) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1035) 
     at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1176) 
     at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1173) 
     at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:859) 
     at com.sun.tools.javac.main.Main.compile(Main.java:523) 
     at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129) 
     at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138) 
     at example.Simple.main(Simple.java:44) 
    Caused by: java.lang.ClassNotFoundException: com.sun.tools.javac.processing.JavacProcessingEnvironment 
     at java.net.URLClassLoader$1.run(URLClassLoader.java:372) 
     at java.net.URLClassLoader$1.run(URLClassLoader.java:361) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at java.net.URLClassLoader.findClass(URLClassLoader.java:360) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:424) 
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:357) 
     ... 15 more 
1 warning 
Hello, world! 

Widocznie, patrząc na komunikat ostrzegawczy przeszedł przez wyjątku lombok nie zahaczyć dany kompilator poprawnie. Niestety nie udało mi się znaleźć żadnej użytecznej informacji. Mogę tylko myśleć, że może to być lombok niewłaściwe postępowanie z Java JDK 8. Mam rację?

Czy znasz inny sposób obejścia tego problemu?

+0

Czy na pewno masz wersję ** JDK ** zamiast JRE i plik 'tools.jar' znajduje się w ścieżce klasy? – Holger

+0

Witam @Holger i dziękuję za odpowiedź! Tak, mam Java SDK 8, ale jestem na Macu! I najwyraźniej brakuje 'tools.jar' na Macu (zobacz http://stackoverflow.com/questions/5616318/how-do-you-address-the-issue-of-a-missing-tools-jar-in-a -jdk-in-mac-os-x). Dzięki, że wskazałeś mi właściwy kierunek! –

+0

To jest dziwne ... Właśnie sprawdziłem i mam 'tools.jar' w' $ JAVA_HOME/lib/tools.jar'. Jednak nie jest on obecny w środowisku Eclipse ... W rzeczywistości jest to błąd Eclipse: referencyjne środowisko Java skonfigurowane jest jako środowisko JRE. Włączenie 'tools.jar' sprawia, że ​​wszystko działa. Dzięki jeszcze raz! –

Odpowiedz

6

Dzięki Holgerowi udało mi się rozwiązać problem.

Problem został spowodowany przez brak tools.jar w ścieżce klasy. Wynika to z faktu, że Eclipse domyślnie rozpoznaje środowisko Java jako środowisko JRE, a nie JDK.

Co więcej, Java JDK może - lub może nie, w zależności od wersji, którą posiadasz - mieć plik tools.jar.

Jeśli masz Java 7 lub 8, powinieneś mieć taką bibliotekę w $JAVA_HOME/lib/tools.jar.

Jeśli masz Java 6, plik nie jest obecny, ale ta sama funkcjonalność jest dostarczana przez $JAVA_HOME/Classes/classes.jar.

Kompilator jest funkcją dodaną do środowiska Java 6, więc jeśli chcesz go używać i masz starszą wersję Java, powinieneś najpierw zaktualizować swoje środowisko.

Teraz istnieje kilka sposobów włączenia tools.jar (lub classes.jar) do ścieżki klasy projektu; ponieważ używam Gradle, zdecydowałem się przedstawić go jako zależność, jak widać w poniższym fragmencie kodu:

dependencies { 
    compile files("${System.properties['java.home']}/../lib/tools.jar") 
    compile 'org.projectlombok:lombok:1.14.4' 
    testCompile 'junit:junit:4.11' 
} 

Nadzieja to małe wyjaśnienie może pomóc innym ludziom stojącym podobny problem!

Pozdrawiam!

+0

Na wypadek gdyby coś zmieniło się w ciągu tych 3 lat: czy opracowano czystszą metodę rozwiązania tego problemu? Mam podobny problem na IntelliJ w tej chwili, wysłałem [pytanie] (https://stackoverflow.com/questions/46904774/reviewiting-the-conflict-of-lombok-with-java-runtime-compilation) odnoszące się do Ciebie. –

+0

Nie mam pojęcia, przeniosłem się do Pythona w międzyczasie ... miły język, nawiasem mówiąc! –

+0

W moich snach znów jestem w pracy. Nigdy nie miałem takich problemów. –