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

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

问题描述

我正在尝试从 XML 文件中检索一些 Java Bean 的描述.我想用项目 lombok 中的 @Data 对它们进行注释,以自动包含构造函数、equals、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 + " {
" + //
            "    @Override
" + //
            "    public String toString() {
" + //
            "        return "Hello, world!";
" + //
            "    }
" + //
            "}
";
        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是:

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;
" + //
            "public @Data class " + name + " {
" + //
            "    private String name;
" + //
            "}
";
        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);
    }

}

不幸的是,我没有得到预期的输出,而是:

unfortunately I don't get the expected output but rather:

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?

推荐答案

感谢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天全站免登陆