Lombok原理

162 阅读10分钟

SPI 介绍

SPI全称为Service Provider Interface,它本质上就是一个Java接口,其实现类通常由第三方或者扩展包提供。SPI机制常被用来扩展框架以及制作可插拔插件

服务通常由一组接口和(通常是抽象的)类组成。服务提供者是服务的具体实现。提供者中的类通常实现服务本身定义的接口和子类。服务提供者可以以扩展的形式安装在Java平台的实现中,即JAR文件。也可以通过将它们添加到应用程序的类路径或者通过其他特定于平台的方法获得

SPI代码演示

编译过程

从Sun Javac的代码来看,编译过程大致可以分为3个过程:

  1. 解析与填充符号表过程
  2. 插入式注解处理器的注解处理过程
  3. 分析与字节码生成过程

Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,上述3个过程的代码逻辑集中在这个类的compile()和compile2()方法中,下面给出整个编译过程中最关键的几个步骤

public void compile(List<JavaFileObject> var1, List<String> var2, Iterable<? extends Processor> var3) {
        //... 

        this.initProcessAnnotations(var3); //(1)
        this.delegateCompiler = this.processAnnotations( //(4)
            this.enterTrees( //(3)
                this.stopIfError(
                    CompileState.PARSE, 
                    this.parseFiles(var1)  //(2)
                )
            ), 
            var2
        );
        this.delegateCompiler.compile2(); //(5)
        
        //... 
}

private void compile2() {
    //...
    
    this.generate( //(9)
        this.desugar( //(8)
            this.flow( //(7)
                this.attribute( //(6)
                    (Env)this.todo.remove()
                )
            )
        )
    );
    
    //...
}
  • (1):准备过程,初始化插入式注解处理器
  • (2):词法分析,语法分析
  • (3):输入到符号表
  • (4):注解处理
  • (5):分析及字节码生成
  • (6):标注
  • (7):数据流分析
  • (8):解语法糖
  • (9):字节码生成

1.1 解析

解析步骤由上述代码清单中的parseFiles()方法(过程(2))完成,解析步骤包括了经典程序编译原理中的词法分析和语法分析两个过程

词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符都可以成为标记,如int a= b + 2这句代码包含了6个标记,分别是int、a、=、b、+、2,虽然关键字int由3个字符构成,但是它只是一个Token,不可再拆分。在Javac的源码中,词法分析过程由com.sun.tools.javac.parser.Scanner类来实现

语法分析是根据Token序列构造抽象语法树的过程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。在Javac的源码中,语法分析过程由com.sun.tools.javac.parser.Parser类实现,这个阶段产出的抽象语法树由com.sun.tools.javac.tree.JCTree类表示,经过这个步骤之后,编译器就基本不会再对源码文件进行操作了,后续的操作都建立在抽象语法树之上

1.2 填充符号表

完成了语法分析和词法分析之后,下一步就是填充符号表的过程,也就是enterTrees()方法(过程(3))所做的事情。符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,可以把它想象成哈希表中K-V值对的形式(实际上符号表不一定是哈希表实现,可以是有序符号表、树状符号表、栈结构符号表等)。符号表中所登记的信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的说明是否一致)和产生中间代码。在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据

在Javac源代码中,填充符号表的过程由com.sun.tools.javac.comp.Enter类实现,此过程的出口是一个待处理列表(To Do List),包含了每一个编译单元的抽象语法树的顶级节点,以及package-info.java(如果存在的话)的顶级节点

1.3 JSR-269简介

在Javac源码中,插入式注解处理器的初始化过程是在initPorcessAnnotations()方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理

在JDK 1.5之后,Java语言提供了对注解(Annotation)的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。在JDK 1.6中实现了JSR-269规范JSR-269:Pluggable Annotations Processing API(插入式注解处理API)。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理。我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,也就是第一张图中的回环过程。 有了编译器注解处理的标准API后,我们的代码才有可能干涉编译器的行为,由于语法树中的任意元素,甚至包括代码注释都可以在插件之中访问到,所以通过插入式注解处理器实现的插件在功能上有很大的发挥空间。只要有足够的创意,程序员可以使用插入式注解处理器来实现许多原本只能在编码中完成的事情

我们知道编译器在把Java程序源码编译为字节码的时候,会对Java程序源码做各方面的检查校验。这些校验主要以程序“写得对不对”为出发点,虽然也有各种WARNING的信息,但总体来讲还是较少去校验程序“写得好不好”。有鉴于此,业界出现了许多针对程序“写得好不好”的辅助校验工具,如CheckStyle、FindBug、Klocwork等。这些代码校验工具有一些是基于Java的源码进行校验,还有一些是通过扫描字节码来完成

1.4 编译相关的数据结构与API

1.4.1 JCTree

JCTree是语法树元素的基类,包含一个重要的字段pos,该字段用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,即使创建了也没有意义。此外,结合访问者模式,将数据结构与数据的处理进行解耦,部分源码如下:

public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {

    public int pos = -1;

    ...

    public abstract void accept(JCTree.Visitor visitor);

    ...
}

这里重点介绍几个JCTree的子类,Demo会用到

  1. JCStatement:声明语法树节点,常见的子类如下

    • JCBlock:语句块语法树节点
    • JCReturn:return语句语法树节点
    • JCClassDecl:类定义语法树节点
    • JCVariableDecl:字段/变量定义语法树节点
  2. JCMethodDecl:方法定义语法树节点

  3. JCModifiers:访问标志语法树节点

  4. JCExpression:表达式语法树节点,常见的子类如下

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

1.4.2 TreeMaker

TreeMaker用于创建一系列语法树节点,创建时会为创建出来的JCTree设置pos字段,所以必须用上下文相关的TreeMaker对象来创建语法树节点,而不能直接new语法树节点

1.4.2.1 TreeMaker.Modifiers

TreeMaker.Modifiers方法用于创建访问标志语法树节点(JCModifiers),源码如下:

  1. flags:访问标志
  2. annotations:注解列表
public JCModifiers Modifiers(long flags) {
    return Modifiers(flags, List.< JCAnnotation >nil());
}

public JCModifiers Modifiers(long flags,
    List<JCAnnotation> annotations) {
        JCModifiers tree = new JCModifiers(flags, annotations);
        boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0;
        tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos;
        return tree;
}

其中入参flags可以用枚举类型com.sun.tools.javac.code.Flags,且支持拼接(枚举值经过精心设计)

例如,我们可以这样用

treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);

1.4.2.2 TreeMaker.ClassDef

TreeMaker.ClassDef用于创建类定义语法树节点(JCClassDecl),源码如下:

  1. mods:访问标志
  2. name:类名
  3. typarams:泛型参数列表
  4. extending:父类
  5. implementing:接口列表
  6. defs:类定义的详细语句,包括字段,方法定义等等
public JCClassDecl ClassDef(JCModifiers mods,
    Name name,
    List<JCTypeParameter> typarams,
    JCExpression extending,
    List<JCExpression> implementing,
    List<JCTree> defs) {
        JCClassDecl tree = new JCClassDecl(mods,
                                     name,
                                     typarams,
                                     extending,
                                     implementing,
                                     defs,
                                     null);
        tree.pos = pos;
        return tree;
}

1.4.2.3 TreeMaker.MethodDef

TreeMaker.MethodDef用于创建方法定义语法树节点(JCMethodDecl),源码如下:

  1. mods:访问标志
  2. name:方法名
  3. restype:返回类型
  4. typarams:泛型参数列表
  5. params:参数列表
  6. thrown:异常声明列表
  7. body:方法体
  8. defaultValue:默认方法(可能是interface中的那个default)
  9. m:方法符号
  10. mtype:方法类型。包含多种类型,泛型参数类型、方法参数类型,异常参数类型、返回参数类型
public JCMethodDecl MethodDef(JCModifiers mods,
    Name name,
    JCExpression restype,
    List<JCTypeParameter> typarams,
    List<JCVariableDecl> params,
    List<JCExpression> thrown,
    JCBlock body,
    JCExpression defaultValue) {
        JCMethodDecl tree = new JCMethodDecl(mods,
                                       name,
                                       restype,
                                       typarams,
                                       params,
                                       thrown,
                                       body,
                                       defaultValue,
                                       null);
        tree.pos = pos;
        return tree;
}

public JCMethodDecl MethodDef(MethodSymbol m,
    Type mtype,
    JCBlock body) {
        return (JCMethodDecl)
            new JCMethodDecl(
                Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())),
                m.name,
                Type(mtype.getReturnType()),
                TypeParams(mtype.getTypeArguments()),
                Params(mtype.getParameterTypes(), m),
                Types(mtype.getThrownTypes()),
                body,
                null,
                m).setPos(pos).setType(mtype);
}

其中,返回类型填null或者treeMaker.TypeIdent(TypeTag.VOID)都代表返回void类型

1.4.2.4 TreeMaker.VarDef

TreeMaker.VarDef用于创建字段/变量定义语法树节点(JCVariableDecl),源码如下:

  1. mods:访问标志
  2. vartype:类型
  3. init:初始化语句
  4. v:变量符号
public JCVariableDecl VarDef(JCModifiers mods,
    Name name,
    JCExpression vartype,
    JCExpression init) {
        JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null);
        tree.pos = pos;
        return tree;
}

public JCVariableDecl VarDef(VarSymbol v,
    JCExpression init) {
        return (JCVariableDecl)
            new JCVariableDecl(
                Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())),
                v.name,
                Type(v.type),
                init,
                v).setPos(pos).setType(v.type);
}

1.4.2.5  TreeMaker.Ident

TreeMaker.Return用于创建return语句语法树节点(JCReturn),源码如下:

`

public JCReturn Return(JCExpression expr) {
        JCReturn tree = new JCReturn(expr);
        tree.pos = pos;
        return tree;
}

1.4.2.6 TreeMaker.Return

TreeMaker.Return用于创建return语句语法树节点(JCReturn),源码如下:

public JCReturn Return(JCExpression expr) {
        JCReturn tree = new JCReturn(expr);
        tree.pos = pos;
        return tree;
}

1.4.2.7 TreeMaker.Select

TreeMaker.Select用于创建域访问/方法访问(这里的方法访问只是取到名字,方法的调用需要用TreeMaker.Apply)语法树节点(JCFieldAccess),源码如下:

  1. selected:.运算符左边的表达式
  2. selector:.运算符右边的名字
public JCFieldAccess Select(JCExpression selected,
    Name selector) 
{
        JCFieldAccess tree = new JCFieldAccess(selected, selector, null);
        tree.pos = pos;
        return tree;
}

public JCExpression Select(JCExpression base,
    Symbol sym) {
        return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type);
}

1.4.2.8 TreeMaker.NewClass

TreeMaker.NewClass用于创建new语句语法树节点(JCNewClass),源码如下:

  1. encl:不太明白此参数含义
  2. typeargs:参数类型列表
  3. clazz:待创建对象的类型
  4. args:参数列表
  5. def:类定义

| ``` public JCNewClass NewClass(JCExpression encl, List typeargs, JCExpression clazz, List args, JCClassDecl def) { JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def); tree.pos = pos; return tree; }

| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

#### 1.4.2.9  TreeMaker.Apply

TreeMaker.Apply用于创建方法调用语法树节点(JCMethodInvocation),源码如下:

1.  typeargs:参数类型列表
1.  fn:调用语句
1.  args:参数列表

| ```
public JCMethodInvocation Apply(List<JCExpression> typeargs,     JCExpression fn,     List<JCExpression> args) {         JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);         tree.pos = pos;         return tree; } 
``` |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

#### 1.4.2.10 TreeMaker.Assign

TreeMaker.Assign用于创建赋值语句语法树节点(JCAssign),源码如下:

1.  lhs:赋值语句左边表达式
1.  rhs:赋值语句右边表达式

public JCAssign Assign(JCExpression lhs, JCExpression rhs) { JCAssign tree = new JCAssign(lhs, rhs); tree.pos = pos; return tree; }


#### 1.4.2.11 TreeMaker.Exec

TreeMaker.Exec用于创建可执行语句语法树节点(JCExpressionStatement),源码如下:

public JCExpressionStatement Exec(JCExpression expr) { JCExpressionStatement tree = new JCExpressionStatement(expr); tree.pos = pos; return tree; }


例如,TreeMaker.Apply以及TreeMaker.Assign就需要外面包一层TreeMaker.Exec来获得一个JCExpressionStatement

#### 1.4.2.12 TreeMaker.Block

TreeMaker.Block用于创建组合语句语法树节点(JCBlock),源码如下:

1.  flags:访问标志
1.  stats:语句列表

public JCBlock Block(long flags, List stats) { JCBlock tree = new JCBlock(flags, stats); tree.pos = pos; return tree; }