使用 `lombok` 注释和 Java JDK 8 在内存中编译 Java 类

Posted

技术标签:

【中文标题】使用 `lombok` 注释和 Java JDK 8 在内存中编译 Java 类【英文标题】:Compiling a Java Class in memory with `lombok` annotations and Java JDK 8 【发布时间】:2014-09-01 13:49:02 【问题描述】:

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

很遗憾,我无法编译这些类,所以我请求您的帮助!

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

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 是:

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;
    


如果我在第一个代码块中运行示例,我会得到以下输出,如预期的那样:

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


Hello, world!

现在,如果我将 lombok.jar 添加到我的项目中,并包含以下示例:

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() 方法。 另请注意,如果我运行前一个示例,现在我会得到以下信息:

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。我说的对吗?

您知道解决此问题的任何其他方法吗?

【问题讨论】:

你确定你有一个 JDK 而不是 JRE 并且 tools.jar 在类路径中吗? 嗨@Holger,感谢您的回复!是的,我有 Java SDK 8,但我在 Mac 上!显然,Mac 上缺少tools.jar(请参阅***.com/questions/5616318/…)。感谢您为我指明正确的方向! 这很奇怪......我刚刚检查了一下,我在$JAVA_HOME/lib/tools.jar 中有tools.jar。但是,它在 Eclipse 中不存在……这实际上是 Eclipse 的错:参考 Java 环境被配置为 JRE。包括tools.jar 使一切正常。再次感谢! 也许你必须手动将它添加到 Eclipse 项目类路径中…… 仅供参考,最后我告诉gradle将其作为依赖项添加到项目中: dependencies compile files("$System.properties['java.home']/ ../lib/tools.jar") 编译 'org.projectlombok:lombok:1.14.4' testCompile 'junit:junit:4.11' 【参考方案1】:

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

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

最重要的是,Java JDK 可能- 也可能没有,这取决于您拥有的版本- 具有tools.jar 文件。

如果你有 Java 7 或 8,你应该在 $JAVA_HOME/lib/tools.jar 中有这样的库。

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

编译器是 Java 6 添加的一个特性,所以如果你想使用它并且你有一个旧版本的 Java,你应该首先更新你的环境。

现在,有几种方法可以将tools.jar(或classes.jar)包含到项目的类路径中;由于我使用 gradle,所以我决定将其作为依赖项引入,如下面的 sn-p 代码所示:

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

希望这个小小的解释可以帮助其他面临类似问题的人!

干杯!

【讨论】:

以防万一这 3 年内发生了一些变化:是否已开发出更清洁的方法来解决此问题?我现在在 IntelliJ 上遇到了类似的问题,已发布 question 引用您的问题。 不知道,与此同时,我已经迁移到 Python 了……顺便说一句,不错的语言! 在我的梦想中,我又在工作中使用 C#。那里从来没有遇到过这样的问题。 顺便说一句,像添加依赖这样的常见补丁对我不起作用。 当我们迁移到 java 11 时会怎样?我们那里没有 tools.jar。

以上是关于使用 `lombok` 注释和 Java JDK 8 在内存中编译 Java 类的主要内容,如果未能解决你的问题,请参考以下文章

lombok和jdk版本不兼容,将jdk12换成jdk1.8解决

IDE 不显示 Lombok 为 Jackson 注释类生成的 getter 和 setter

JDK和Lombok版本兼容问题

JDK和Lombok版本兼容问题

JDK和Lombok版本兼容问题

Lombok插件