乐趣区

关于java:Lombok-原理与实现

本文次要蕴含以下内容:

  1. Lombok 的实现机制剖析。
  2. 插入式注解处理器的阐明及应用。
  3. 入手实现 lombok 的 @Getter 和 @Setter 注解。
  4. 配置 IDEA 以调试 Java 编译过程。

1. Lombok

官网介绍:

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

在 Maven 中增加 Lombok 依赖:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <scope>provided</scope>
</dependency>

简略的例子如下,通过增加注解的形式,主动生成 getter/setter 办法。

package com.sumkor;

import lombok.Getter;
import lombok.Setter;

/**
 * @author Sumkor
 * @since 2021/12/27
 */
@Setter
@Getter
public class MyTest {

    private String value;

    public static void main(String[] args) {MyTest myTest = new MyTest();
        myTest.setValue("hello");
        System.out.println(myTest.getValue());
    }
}

编译后的代码如下:

package com.sumkor;

public class MyTest {
    private String value;

    public MyTest() {}

    public static void main(String[] args) {MyTest myTest = new MyTest();
        myTest.setValue("hello");
        System.out.println(myTest.getValue());
    }

    public void setValue(String value) {this.value = value;}

    public String getValue() {return this.value;}
}

2. Annotation Processor

2.1 Javac 编译器

《深刻了解 Java 虚拟机:JVM 高级个性与最佳实际》中的第 10 章《前端编译与优化》对 Javac 编译器进行了介绍。

Javac 编译过程如下:

从 Javac 代码的总体构造来看,编译过程大抵能够分为 1 个筹备过程和 3 个处理过程,它们别离如下所示。

  1. 筹备过程:初始化插入式注解处理器。
  2. 解析与填充符号表过程,包含:词法、语法分析;填充符号表。
  3. 插入式注解处理器的注解处理过程。
  4. 剖析与字节码生成过程。

重点关注对插入式注解处理器的阐明:

JDK 5 之后,Java 语言提供了对注解(Annotations)的反对,注解在设计上本来是与一般的 Java 代码一样,都只会在程序运行期间发挥作用的。但在 JDK 6 中又提出并通过了 JSR-269 提案,该提案设计了一组被称为“插入式注解处理器”的规范 API,能够提前至编译期对代码中的特定注解进行解决,从而影响到前端编译器的工作过程。咱们能够把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,容许读取、批改、增加形象语法树中的任意元素。如果这些插件在解决注解期间对语法树进行过批改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行批改为止。

能够看到 Lombok 是基于插入式注解处理器来实现:

有了编译器注解解决的规范 API 后,程序员的代码才有可能干预编译器的行为,因为语法树中的任意元素,甚至包含代码正文都能够在插件中被拜访到,所以通过插入式注解处理器实现的插件在性能上有很大的施展空间。只有有足够的创意,程序员能应用插入式注解处理器来实现许多本来只能在编码中由人工实现的事件。譬如 Java 驰名的编码效率工具 Lombok,它能够通过注解来实现主动产生 getter/setter 办法、进行空置查看、生成受查异样表、产生 equals() 和 hashCode() 办法,等等,帮忙开发人员打消 Java 的简短代码,这些都是依赖插入式注解处理器来实现的。

2.2 Java 注解

Java 中的注解分为运行时注解和编译时注解,通过设置元注解 @Retention 中的 RetentionPolicy 指定注解的保留策略:

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

阐明:

  • SOURCE:示意注解的信息会被编译器摈弃,不会留在 class 文件中,注解的信息只会留在源文件中。
  • CLASS:示意注解的信息被保留在 class 文件中。当程序编译时,但不会被虚拟机读取在运行的时候。
  • RUNTIME:示意注解的信息被保留在 class 文件中。当程序编译时,会被虚拟机保留在运行时。

日常开发中应用的注解都是 RUNTIME 类型的,能够在运行期被反射调用读取。

javax.annotation.Resource

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource

而 Lombok 中的注解是 SOURCE 类型的,只会在编译期间被插入式注解处理器读取。

lombok.Getter

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter

2.3 插入式注解处理器

自定义的注解处理器须要继承 AbstractProcessor 这个类,根本的框架大体如下:

package com.sumkor.processor;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedAnnotationTypes("com.sumkor.annotation.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {return true;}
}

其中:

  • @SupportedAnnotationTypes 代表了这个注解处理器对哪些注解感兴趣,能够应用星号 * 作为通配符代表对所有的注解都感兴趣。
  • @SupportedSourceVersion 指出这个注解处理器能够解决哪些版本的 Java 代码。
  • init() 用于获取编译阶段的一些环境信息。
  • process() 能够编写解决语法树的具体逻辑。如果不须要扭转或增加形象语法树中的内容,process() 办法就能够返回一个值为 false 的布尔值,告诉编译器这个轮次中的代码未发生变化。

本文后续会实现具体的自定义的插入式注解处理器,并进行打断点调试。

2.4 Javac APT

利用插入式注解处理器在编译阶段批改语法树,须要用到 Javac 中的注解解决工具 APT(Annotation Processing Tool),这是 Sun 为了帮忙注解的处理过程而提供的工具,APT 被设计为操作 Java 源文件,而不是编译后的类。

本文应用的是 JDK 8,Javac 相干的源码寄存在 tools.jar 中,要在程序中应用的话就必须把这个库放到类门路上。留神,到了 JDK 9 时,整个 JDK 所有的 Java 类库都采纳模块化进行重构划分,Javac 编译器就被挪到了 jdk.compiler 模块,并且对该模块的拜访进行了严格的限度。

2.5 JCTree 语法树

com.sun.tools.javac.tree.JCTree 是语法树元素的基类,蕴含以下重要的子类:

  • JCStatement:申明语法树节点,常见的子类如下

    • JCBlock:语句块语法树节点
    • JCReturn:return 语句语法树节点
    • JCClassDecl:类定义语法树节点
    • JCVariableDecl:字段 / 变量定义语法树节点
  • JCMethodDecl:办法定义语法树节点
  • JCModifiers:拜访标记语法树节点
  • JCExpression:表达式语法树节点,常见的子类如下

    • JCAssign:赋值语句语法树节点
    • JCIdent:标识符语法树节点,能够是变量,类型,关键字等

JCTree 利用的是访问者模式,将数据与数据的解决进行解耦。局部源码如下:

public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {

    public int pos = -1;

    public abstract void accept(JCTree.Visitor visitor);

}

利用访问者 TreeTranslator,能够拜访 JCTree 上的类定义节点 JCClassDecl,进而能够获取类中的成员变量、办法等节点并进行批改。

编码过程中,能够利用 javax.annotation.processing.Messager 来打印编译过程的相干信息。
留神,Messager 的 printMessage 办法在打印 log 的时候会主动过滤反复的 log 信息。

比起打印日志,利用 IDEA 工具对编译过程进行 debug,对 JCTree 语法树会有更为直观的意识。
文末提供了在 IDEA 中调试插入式注解处理器的配置。

3. 入手实现

别离创立两个我的项目,用于实现和验证 @Getter 和 @Setter 注解。

创立我的项目 lombok-processor,蕴含自定义注解和插入式注解处理器。
创立我的项目 lombok-app,该我的项目依赖了 lombok-processor 我的项目,应用其中的自定义注解进行测试。

3.1 processor 我的项目

我的项目整体构造如下:

Maven 配置

因为须要在编译阶段批改 Java 语法树,须要调用语法树相干的 API,因而将 JDK 目录下的 tools.jar 引入以后我的项目。

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8</version>
    <scope>system</scope>
    <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

lombok-processor 我的项目采纳 Java SPI 机制,使其自定义的插入式注解处理器对 lombok-app 我的项目失效。因为 lombok-processor 我的项目在编译期间须要排除掉本身的插入式注解处理器,因而配置 maven resource 以过滤掉 SPI 文件,等到打包的时候,再将 SPI 文件退出 lombok-processor 我的项目的 jar 包中。
此外,为了不便调试,将 lombok-processor 我的项目的源码也公布到本地仓库中。

残缺的 maven build 配置如下:

<build>
    <!-- 配置一下 resources 标签,过滤掉 META-INF 文件夹,这样在编译的时候就不会找到 services 的配置 -->
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <exclude>META-INF/**/*</exclude>
            </excludes>
        </resource>
    </resources>

    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>${maven.compiler.target}</source>
                <target>${maven.compiler.target}</target>
            </configuration>
        </plugin>
        <!-- 在打包前 (prepare-package 生命周期) 再把 services 文件夹从新拷贝过去 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.6</version>
            <executions>
                <execution>
                    <id>process-META</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>target/classes</outputDirectory>
                        <resources>
                            <resource>
                                <directory>${basedir}/src/main/resources/</directory>
                                <includes>
                                    <include>**/*</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <!-- Source attach plugin -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.1</version>
            <configuration>
                <attach>true</attach>
            </configuration>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

自定义注解

自定义注解次要应用了两个元注解:

  • @Target({ElementType.TYPE}) 示意是对类的注解。
  • @Retention(RetentionPolicy.SOURCE)示意这个注解只在编译期起作用,在运行时将不存在。

自定义 @Getter 注解:

package com.sumkor.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE}) 
@Retention(RetentionPolicy.SOURCE) 
public @interface Getter {}

自定义 @Setter 注解:

package com.sumkor.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {}

自定义注解处理器

BaseProcessor

定义形象的基类 BaseProcessor,用于对立获取编译阶段的工具类,如 JavacTrees、TreeMaker 等。
因为本我的项目须要在 IDEA 中进行调试和运行,因而引入 IDEA 环境的 ProcessingEnvironment。

package com.sumkor.processor;

import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import java.lang.reflect.Method;

/**
 * @author Sumkor
 * @since 2021/12/27
 */
public abstract class BaseProcessor extends AbstractProcessor {

    protected Messager messager;   // 用来在编译期打 log 用的
    protected JavacTrees trees;    // 提供了待处理的形象语法树
    protected TreeMaker treeMaker; // 封装了创立 AST 节点的一些办法
    protected Names names;         // 提供了创立标识符的办法

    /**
     * 获取编译阶段的一些环境信息
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {processingEnv = jbUnwrap(ProcessingEnvironment.class, processingEnv);
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    /**
     * 获取 IDEA 环境下的 ProcessingEnvironment
     */
    private static <T> T jbUnwrap(Class<? extends T> iface, T wrapper) {
        T unwrapped = null;
        try {final Class<?> apiWrappers = wrapper.getClass().getClassLoader().loadClass("org.jetbrains.jps.javac.APIWrappers");
            final Method unwrapMethod = apiWrappers.getDeclaredMethod("unwrap", Class.class, Object.class);
            unwrapped = iface.cast(unwrapMethod.invoke(null, iface, wrapper));
        }
        catch (Throwable ignored) {}
        return unwrapped != null? unwrapped : wrapper;
    }

}

GetterProcessor

自定义注解 @Getter 对应的注解处理器如下,代码流程:

  1. 获取被 @Getter 注解润饰的类。
  2. 找到类上的所有成员变量。
  3. 为成员变量结构 getter 办法。

难点在于对 JavacTrees 和 TreeMaker 相干 API 的应用上,文中要害代码均有正文,不便了解。

package com.sumkor.processor;

import com.sumkor.annotation.Getter;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

/**
 * @author Sumkor
 * @since 2021/12/24
 */
@SupportedAnnotationTypes("com.sumkor.annotation.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends BaseProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);
        messager.printMessage(Diagnostic.Kind.NOTE, "========= GetterProcessor init =========");
    }

    /**
     * 对 AST 进行解决
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 获取被自定义 Getter 注解润饰的元素
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
        set.forEach(element -> {
            // 依据元素获取对应的语法树 JCTree
            JCTree jcTree = trees.getTree(element);
            jcTree.accept(new TreeTranslator() {
                // 解决语法树的类定义局部 JCClassDecl
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    for (JCTree tree : jcClassDecl.defs) {
                        // 找到语法树上的成员变量节点,存储到 jcVariableDeclList 汇合
                        if (tree.getKind().equals(Tree.Kind.VARIABLE)) {JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }
                    // 为成员变量结构 getter 办法,并增加到 JCClassDecl 之中
                    jcVariableDeclList.forEach(jcVariableDecl -> {messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + "has been processed");
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                    });
                    super.visitClassDef(jcClassDecl);
                }

            });
        });
        return true;
    }

    /**
     * 为成员遍历结构 getter 办法
     */
    private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        // 生成表达式 return this.value;
        statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
        // 加上大括号 {return this.value;}
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        // 组装办法
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
    }

    /**
     * 驼峰命名法
     */
    private Name getNewMethodName(Name name) {String s = name.toString();
        return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }

}

SetterProcessor

自定义注解 @Setter 对应的注解处理器 SetterProcessor,整体流程与 GetterProcessor 差不多:

  1. 获取被 @Setter 注解润饰的类。
  2. 找到类上的所有成员变量。
  3. 为成员变量结构 setter 办法。

重点关注结构 setter 办法的逻辑:

/**
 * 为成员结构 setter 办法
 */
private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
    // 生成表达式 this.value = value;
    JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
    statements.append(aThis);
    // 加上大括号 {this.value = value;}
    JCTree.JCBlock block = treeMaker.Block(0, statements.toList());

    // 生成办法参数之前,指明以后语法节点在语法树中的地位,防止出现异常 java.lang.AssertionError: Value of x -1
    treeMaker.pos = jcVariableDecl.pos;

    // 生成办法参数 String value
    JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.getName(), jcVariableDecl.vartype, null);
    List<JCTree.JCVariableDecl> parameters = List.of(param);
    // 生成返回对象 void
    JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());

    // 组装办法
    return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), methodType, List.nil(), parameters, List.nil(), block, null);
}

/**
 * 赋值操作 lhs = rhs
 */
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
    return treeMaker.Exec(treeMaker.Assign(lhs, rhs)
    );
}

/**
 * 驼峰命名法
 */
private Name getNewMethodName(Name name) {String s = name.toString();
    return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}

3.2 app 我的项目

我的项目整体构造如下,这里 IDEA 的标红提醒是因为无奈辨认自定义注解导致的,不影响我的项目运行。

在 maven 中引入 lombok-processor 我的项目:

<dependency>
    <groupId>com.sumkor</groupId>
    <artifactId>lombok-processor</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

编写测试类,引入自定义的注解 @Getter 和 @Setter,能够看到 IDEA 尽管标红提醒找不到办法,然而能够失常通过编译和运行。

4. 调试

4.1 IDEA 配置

应用 Attach Remote JVM 的形式,对 lombok-app 我的项目的编译过程进行调试。

  1. 创立一个近程调试,指定端口为 5005。
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

  1. 在菜单中选择:Help -> Edit Custom VM Options,增加以下内容并重启 IDEA。这里的端口是上一步中抉择须要 attach 的端口。
-Dcompiler.process.debug.port=5005
  1. 关上 IDEA 的 Debug Build Process。留神,这个选项在每次 IDEA 重启后都会默认敞开。

4.2 开始调试

  1. 在注解处理器中增加断点,举荐断点打在 AbstractProcessor#init 中。
  2. 执行 mvn clean 操作,革除掉上一次构建实现的内容。
  3. 执行 ctrl + F9 操作进行 lombok-app 我的项目构建。

    在状态栏能够看到构建过程在期待 debugger 连贯:
  4. 运行方才创立的近程调试。

    能够看到进入了 AbstractProcessor#init 中的断点。
    对自定义的注解处理器进行调试,察看 TreeMaker 对语法树的批改后果。

    4.3 问题解决

在编写 SetterProcessor#process 办法的时候,如果短少 treeMaker.pos = jcVariableDecl.pos; 这一行代码,在编译过程会报错提醒:java.lang.AssertionError: Value of x -1,具体编译信息如下:

Executing pre-compile tasks...
Loading Ant configuration...
Running Ant tasks...
Running 'before' tasks
Checking sources
Copying resources... [lombok-app]
Parsing java... [lombok-app]
java: ========= GetterProcessor init =========
java: value has been processed
java: ========= SetterProcessor init =========
java: 编译器 (1.8.0_91) 中出现异常谬误。如果在 Bug Database (http://bugs.java.com) 中没有找到该谬误, 请通过 Java Bug 报告页 (http://bugreport.java.com) 建设该 Java 编译器 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。java: java.lang.AssertionError: Value of x -1
java:     at com.sun.tools.javac.util.Assert.error(Assert.java:133)
java:     at com.sun.tools.javac.util.Assert.check(Assert.java:94)
java:     at com.sun.tools.javac.util.Bits.incl(Bits.java:186)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.initParam(Flow.java:1858)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.visitMethodDef(Flow.java:1807)
java:     at com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:778)
java:     at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:49)
java:     at com.sun.tools.javac.comp.Flow$BaseAnalyzer.scan(Flow.java:404)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.scan(Flow.java:1382)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.visitClassDef(Flow.java:1749)
java:     at com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:693)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.analyzeTree(Flow.java:2446)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.analyzeTree(Flow.java:2429)
java:     at com.sun.tools.javac.comp.Flow.analyzeTree(Flow.java:211)
java:     at com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1327)
java:     at com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1296)
java:     at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:901)
java:     at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:860)
java:     at com.sun.tools.javac.main.Main.compile(Main.java:523)
java:     at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129)
java:     at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138)
java:     at org.jetbrains.jps.javac.JavacMain.compile(JavacMain.java:238)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.lambda$compileJava$2(JavaBuilder.java:514)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.invokeJavac(JavaBuilder.java:560)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.compileJava(JavaBuilder.java:512)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.compile(JavaBuilder.java:355)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.doBuild(JavaBuilder.java:280)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.build(JavaBuilder.java:234)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.runModuleLevelBuilders(IncProjectBuilder.java:1485)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.runBuildersForChunk(IncProjectBuilder.java:1123)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.buildTargetsChunk(IncProjectBuilder.java:1268)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.buildChunkIfAffected(IncProjectBuilder.java:1088)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.buildChunks(IncProjectBuilder.java:854)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.runBuild(IncProjectBuilder.java:441)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.build(IncProjectBuilder.java:190)
java:     at org.jetbrains.jps.cmdline.BuildRunner.runBuild(BuildRunner.java:132)
java:     at org.jetbrains.jps.cmdline.BuildSession.runBuild(BuildSession.java:318)
java:     at org.jetbrains.jps.cmdline.BuildSession.run(BuildSession.java:146)
java:     at org.jetbrains.jps.cmdline.BuildMain$MyMessageHandler.lambda$channelRead0$0(BuildMain.java:218)
java:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java:     at java.lang.Thread.run(Thread.java:745)
java: Compilation failed: internal java compiler error
Checking dependencies... [lombok-app]
Dependency analysis found 0 affected files
Errors occurred while compiling module 'lombok-app'
javac 1.8.0_91 was used to compile java sources
Finished, saving caches...
Compilation failed: errors: 1; warnings: 0
Executing post-compile tasks...
Loading Ant configuration...
Running Ant tasks...
Synchronizing output directories...

从异样堆栈信息可知是 com.sun.tools.javac.util.Bits.incl(Bits.java:186) 呈现报错,定位到异样地位:

com.sun.tools.javac.util.Bits#incl

public void incl(int var1) {Assert.check(this.currentState != Bits.BitsState.UNKNOWN);
    Assert.check(var1 >= 0, "Value of x" + var1); // 这一行报错
    this.sizeTo((var1 >>> 5) + 1);
    this.bits[var1 >>> 5] |= 1 << (var1 & 31);
    this.currentState = Bits.BitsState.NORMAL;
}

打断点剖析异样链路,能够定位到是 SetterProcessor#process 办法导致的报错。导致问题的外围值是 JCTree 的 pos 字段,该字段用于指明以后语法树节点在语法树中的地位,而应用 TreeMaker 生成的 pos 都为固定值,须要将此字段设置为所解析元素的 pos 即可(批改后的 SetterProcessor#process 办法见上一节)。

5. 参考

  • Lombok 原理剖析与性能实现
  • Java-JSR-269- 插入式注解处理器
  • Lombok 常常用,然而你晓得它的原理是什么吗?
  • jsr269 java.lang.AssertionError: Value of x -1

作者:Sumkor
链接:https://segmentfault.com/a/11…

退出移动版