使用`lombok'注释和Java JDK 8在内存中编译Java类 [英] Compiling a Java Class in memory with `lombok` annotations and Java JDK 8

查看:6703
本文介绍了使用`lombok'注释和Java JDK 8在内存中编译Java类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试从XML文件中检索几个Java Bean的描述。
我想用项目 lombok 中的 @Data 注释它们以自动包含构造函数, hashCode,getter,setter和toString。
我想在内存中编译它们,生成几个实例(使用同一个XML文件中的数据),并将它们添加到Drools中,最终对该数据做一些推理。

I'm trying to retrieve the description of a few Java Beans from an XML file. I'd like to annotate them with @Data from project lombok to automatically include constructor, equals, hashCode, getters, setters and toString. I'd like to compile them in memory, generate a few instances (with data from the same XML file) and add them to Drools to eventually do some reasoning on that data.

不幸的是,我无法编译这些类,所以我要求您的帮助!

Unfortunately, I cannot compile those classes and so I am asking for your help!

以下代码显示如何以编程方式在内存中编译Java类:

The following code shows how to programmatically compile Java classes in memory:

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

}

其中 MemoryFileManager 是:

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

}

MemoryJavaClassObject 是:

and MemoryJavaClassObject is:

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

}

最后 MemoryJavaFileObject 是:

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

}

第一个代码块,我得到以下输出,如预期:

If I run the example in the first code block, I get the following output, as expected:

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

Hello, world!

现在,如果我添加 lombok.jar 进入我的项目,我包括以下示例:

Now, if I add the lombok.jar into my project and I include the following example:

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

}

预期输出,而是:

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
Person@39aeed2f

请注意,编译并且执行默认的 toString()方法,因为显示了典型的输出。还要注意,如果我运行前例,现在我得到以下:

Notice that the class gets compiled and the default toString() method is executed since the typical output is displayed. Also notice that if I run the former example, now I get the following:

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!

显然,通过查看异常传递的警告消息, lombok 没有正确钩住给定的编译器。不幸的是,我不能找到任何有用的信息。我只能认为它可以 lombok 不能正确处理Java JDK 8.我是对吗?

Apparently, by looking at the warning message passed by the exception, lombok doesn't hook the given compiler properly. Unfortunately I was not able to find any useful bit of information. I can only think it could be lombok not dealing properly with Java JDK 8. Am I right?

你知道解决这个问题的任何其他方法吗?

Do you know any other way to work around this problem?

推荐答案

p>感谢Holger,我成功地解决了这个问题。

Thanks to Holger, I successfully solved the problem.

这个问题是由类路径中没有 tools.jar 引起的。
这是因为Eclipse默认将Java环境识别为JRE而不是JDK。

The issue was caused by the absence of tools.jar in the class path. This is due to the fact that Eclipse by default recognises the Java environment as a JRE instead of a JDK.

除此之外,Java JDK可能 - 或者可能不会,具体取决于您拥有的版本 tools.jar 文件。

On top of that, Java JDK may - or may not, depending on which version you have - have the tools.jar file.

如果您有Java 7或8,您应该在 $ JAVA_HOME / lib / tools.jar

If you have Java 7 or 8, you should have such library in $JAVA_HOME/lib/tools.jar.

如果您有Java 6,该文件不存在,但相同的功能由 $ JAVA_HOME / Classes / classes.jar

If you have Java 6, the file is not present but the same functionality is provided by $JAVA_HOME/Classes/classes.jar.

编译器是使用Java 6添加的功能,因此如果要使用它,并且使用较早版本的Java,首先更新您的环境。

The compiler is a feature added with Java 6, so if you want to use it and you have an older version of Java, you should update your environment first.

现在,有几种方法可以包含 tools.jar (或 classes.jar )到您的项目的类路径;因为我使用gradle,我决定引入它作为依赖,如下面的代码片段所示:

Now, there are several ways to include tools.jar (or classes.jar) into your project's class path; since I use gradle, I decided to introduce it as a dependency, as you can see in the following snippet of code:

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

帮助其他人面临类似的问题!

Hope this little explanation might help other people facing a similar problem!

干杯!

这篇关于使用`lombok'注释和Java JDK 8在内存中编译Java类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆