动手实现Lombok

动手实现Lombok


Javac编译器

提到编译器,可能很多后端研发工程师只是在脑海中有一个模糊的概念,我也是这样,因为业务开发中几乎使用不到。我们都工作在编译器的上层。

对于Java语言来说,它的整个编译期是不确定的过程,我们写的.java类文件需要被编译成.class字节码文件然后加载至虚拟机中,.class字节码文件需要被编译成机器码进行执行,当然也有直接将.java类文件编译成机器码的编译器。

Javac编译器是一个将.java文件编译成.class字节码的编译器,它可以在不改变底层虚拟机的情况下对程序编码进行编译期的优化,即:语法糖。

sun公司提供的Javac编译器代码(源码在JDKtool.jar包中)实现来看,编译的过程可分为以下三个阶段:

  • Parse and Enter:解析与填充符号表,编译器会将Java文件解析成Abstract Syntax TreeAST)抽象语法树,这个过程会校验语法的正确性等。
  • Annotation Processing:插入式注解处理器的注解处理,这个过程可以对语法树进行相关操作。
  • Analyse and Generate:分析语法树与字节码生成。

JDK5后,Java语言引入了注解,这些注解与普通的Java类一样,是在运行期间发挥作用的。JDK6实现了JSR-269规范,提供出一组插入式注解处理器的标准API在编译期间对注解进行处理,我们可以把它看作是一组编译器的插件,或者称为钩子,我们可以使用这些API对第一阶段产生的语法树进行修改,一旦语法树被修改,编译器将会回到第一阶段重新解析与填充符号表,直到所有的插入式注解处理器都没有再对语法树进行修改为止,每一次回到第一阶段称为一次Round

Javac编译器的源码是com.sun.tools.javac.main.JavaCompiler类,编译的方法是com.sun.tools.javac.main.JavaCompiler#compile,其中插入注解处理器的初始化过程是在this.initProcessAnnotations(var3);方法中完成的;执行过程是在com.sun.tools.javac.main.JavaCompiler#processAnnotations方法中完成的,该方法中判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment#doProcessing方法得到一个新的JavaCompiler类对象继续后续的处理。

Lombok的原理

Lombok正是利用Javac编译器的第二阶段进行实现的,使用的也正是JDK6提供的JSR-269实现。在编译期操作语法树,实现字节码的动态生成。

造轮子

我们要实现的是一个简易的Lombok,一个注解在编译期同时生成gettersetter方法。

自定义注解

首先要定义一个注解@GetterAndSetter

1
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.*;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2020/12/15
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface GetterAndSetter {
}

自定义注解处理器

然后定义一个处理@GetterAndSetter注解的处理器GetterAndSetterProcessor,所有的处理器都建议去继承javax.annotation.processing.AbstractProcessor抽象类,它提供了一些基本实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

import com.google.auto.service.AutoService;
import com.sun.source.util.Trees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;
import org.sunchaser.lombok.impl.annotation.GetterAndSetter;

import javax.annotation.processing.*;
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;

/**
* 自定义@GetterAndSetter注解的插入处理器
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2020/12/15
*/
@SupportedAnnotationTypes({"org.sunchaser.lombok.impl.annotation.GetterAndSetter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class GetterAndSetterProcessor extends AbstractProcessor {

/**
* 可将Element转换成JCTree,组成AST语法树。使用Trees.instance(processingEnv);进行初始化。
*/
private Trees trees;

/**
* 构造JCTree的工具类。使用TreeMaker.instance(((JavacProcessingEnvironment) processingEnv).getContext());初始化。
*/
private TreeMaker treeMaker;

/**
* 名字处理工具类。使用Names.instance(context);进行初始化。
*/
private Names names;

/**
* 编译期的日志打印工具类。使用processingEnv.getMessager();进行初始化
*/
private Messager messager;

/**
* init 初始化
* @param processingEnv 环境:提供一些Javac的执行工具
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.trees = Trees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}

/**
* 具体的执行
* @param annotations 注解集合
* @param roundEnv 执行round环境
* @return 执行结果
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE,"roundEnv --->" + roundEnv);
if (!roundEnv.processingOver()) {
// 所有@GetterAndSetter注解标注的类
Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(GetterAndSetter.class);
annotated.forEach(element -> {
// 获得当前遍历类的语法树
JCTree tree = (JCTree) trees.getTree(element);
// 使用GetterAndSetterTreeTranslator处理
tree.accept(new GetterAndSetterTreeTranslator(treeMaker, names, messager));
});
}
// 返回true:Javac编译器会从编译期的第二阶段回到第一阶段
return true;
}

/**
* 获取当前处理器支持的注解类型集合
*
* JDK7后可用注解@SupportedAnnotationTypes代替该方法
*
* @return 注解类全限定类名字符串的集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}

/**
* 获取支持的JDK版本
*
* JDK7后可用注解@SupportedSourceVersion代替该方法
*
* @return JDK版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}

注解处理器的实现细节

主要方法

我们来看下子类中实现的主要方法:

init(ProcessingEnvironment processingEnv)

初始化方法,在父抽象类中对入参ProcessingEnvironment对象进行了初始化,我们可以通过这个对象得到很多工具类,例如TreesTreeMakerNames等等。

process

父类中的抽象方法,子类进行具体的处理实现。输入参数RoundEnvironment代表每一次Round的环境。

getSupportedAnnotationTypes

指定当前处理器是用来处理哪个注解的,返回值是需要处理的注解的全限定类名字符串的集合。在JDK7后,父抽象类基于@SupportedAnnotationTypes注解进行了实现,子类中可不重写该方法,直接使用注解:@SupportedAnnotationTypes({"org.sunchaser.lombok.impl.annotation.GetterAndSetter"})即可。

getSupportedSourceVersion

指定支持的JDK版本,这里通常返回SourceVersion.latestSupported()最新版本。在JDK7后,父抽象类基于@SupportedSourceVersion注解进行了实现,子类中可不重新进行实现,直接使用注解:@SupportedSourceVersion(SourceVersion.RELEASE_8)即可。

工具类介绍

下面介绍一下init方法中初始化的一些工具类:

javax.annotation.processing.Messager

编译期的日志打印工具类。使用processingEnv.getMessager();进行初始化。

javax.lang.model.element.Element

对一个.java文件结构的建模。一个Java类被划分为各个Element,每个部分都有对应的子类实现。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
package com.sunchaser;                  /** PackageElement      **/

public class Foo { /** TypeElement **/
private String str; /** VariableElement **/
private Foo foo; /** VariableElement **/
public Foo() {} /** ExecuteableElement **/
public void setStr(
String str /** TypeElement **/
) {
this.str = str; /** ExecuteableElement **/
}
}
com.sun.source.util.Trees

可将Element转换成JCTree,组成AST语法树。使用Trees.instance(processingEnv);进行初始化。

com.sun.tools.javac.tree.TreeMaker

构造JCTree的工具类。使用TreeMaker.instance(((JavacProcessingEnvironment) processingEnv).getContext());进行初始化。

com.sun.tools.javac.util.Names

名字处理工具类。使用Names.instance(context);进行初始化。无论是类、方法、变量或者参数的名称都需要此类来创建,常用形式为names.fromString("setNameXXX")

com.sun.tools.javac.util.List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class List<A> extends AbstractCollection<A> implements java.util.List<A> {
public A head;
public List<A> tail;

/** nil()方法返回的空集合 **/
private static final List<?> EMPTY_LIST = new List<Object>((Object)null, (List)null) {
public List<Object> setTail(List<Object> var1) {
throw new UnsupportedOperationException();
}

public boolean isEmpty() {
return true;
}
};

/**
* 返回空集合
*/
public static <A> List<A> nil() {
return EMPTY_LIST;
}

/** 以下是一系列重载的of方法,用来创建List **/
public static <A> List<A> of(A var0) {
return new List(var0, nil());
}

public static <A> List<A> of(A var0, A var1) {
return new List(var0, of(var1));
}

public static <A> List<A> of(A var0, A var1, A var2) {
return new List(var0, of(var1, var2));
}

public static <A> List<A> of(A var0, A var1, A var2, A... var3) {
return new List(var0, new List(var1, new List(var2, from(var3))));
}

....
}

在这套抽象语法树的实现中,所有的List都使用的是com.sun.tools.javac.util.List,而不是java.util.ArrayList。这是一个链式List,尾节点为集合。常用的方法有List.nil()List.of()

JCTree介绍

JCTree是语法树元素的基类,包含一个重要的字段pos,该字段用来指明当前语法树节点JCTree在语法树中的位置。com.sun.tools.javac.tree.JCTree是一个抽象类,且子类的构造函数都是protected的,所以不能直接使用new关键字创建语法树元素节点,而是使用访问者设计模式进行创建。

下面介绍几个重要的子类:

  • com.sun.tools.javac.tree.JCTree.JCStatement:声明语法树节点,仍然是一个抽象类,下面是常用子类:
    • com.sun.tools.javac.tree.JCTree.JCBlock:语句块语法树节点。
    • com.sun.tools.javac.tree.JCTree.JCReturnreturn语句语法树节点。
    • com.sun.tools.javac.tree.JCTree.JCClassDecl:类定义语法树节点。
    • com.sun.tools.javac.tree.JCTree.JCVariableDecl:字段/变量定义语法树节点。
  • com.sun.tools.javac.tree.JCTree.JCMethodDecl:方法定义语法树节点。
  • com.sun.tools.javac.tree.JCTree.JCModifiers:访问标志语法树节点。
  • com.sun.tools.javac.tree.JCTree.JCExpression:表达式语法树节点,仍然是一个抽象类,下面是常用子类:
    • com.sun.tools.javac.tree.JCTree.JCAssign:赋值语句语法树节点。
    • com.sun.tools.javac.tree.JCTree.JCIdent:标志符语法树节点,可以是变量、类型和关键字等等。

TreeMaker介绍

TreeMaker用于创建一系列的语法树节点,上面说到创建JCTree语法树节点不能直接使用new关键字创建,所以才有了TreeMaker工具类,它会在创建时为JCTree对象设置pos字段,必须使用上下文相关的TreeMaker对象来创建语法树节点。

下面介绍几个常用方法:

TreeMaker.Modifiers

用于创建访问标志的语法树节点JCModifiers,主要方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public JCModifiers Modifiers(long flags) {
return this.Modifiers(flags, List.nil());
}

/**
* 用于创建访问标志的语法树节点
* @param flags 访问标志
* @param annotations 注解列表
*/
public JCModifiers Modifiers(long flags, List<JCAnnotation> annotations) {
JCModifiers tree = new JCModifiers(flags, annotations);
boolean noFlags = (flags & 8796093033983L) == 0L;
tree.pos = noFlags && annotations.isEmpty() ? -1 : this.pos;
return tree;
}

其中flags可以使用枚举类com.sun.tools.javac.code.Flags,例如treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL)创建的语法树节点为:public static final

TreeMaker.ClassDef

用于创建类定义的语法树节点JCClassDecl,主要方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 用于创建类定义的语法树节点
* @param modifiers 访问标识,可通过treeMaker.Modifiers创建
* @param name 类名
* @param typeParameter 泛型参数列表
* @param extending 父类
* @param implementing 实现的接口集合
* @param defs 类定义的详细语句,包括字段、方法的定义等等
*/
public JCClassDecl ClassDef(JCModifiers modifiers,
Name name,
List<JCTypeParameter> typeParameter,
JCExpression extending,
List<JCExpression> implementing,
List<JCTree> defs) {
JCClassDecl tree = new JCClassDecl(modifiers, name, typeParameter, extending, implementing, defs, (ClassSymbol)null);
tree.pos = this.pos;
return tree;
}
TreeMaker.VarDef

用于创建字段/变量定义的语法树节点JCVariableDecl,主要方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 用于创建字段/变量定义的语法树节点
* @param modifiers 访问标志,可通过treeMaker.Modifiers创建
* @param name 字段/变量名称
* @param varType 字段/变量类型
* @param init 字段/变量初始化语句
*/
public JCVariableDecl VarDef(JCModifiers modifiers,
Name name,
JCExpression varType,
JCExpression init) {
JCVariableDecl tree = new JCVariableDecl(modifiers, name, varType, init, (VarSymbol)null);
tree.pos = this.pos;
return tree;
}
TreeMaker.Ident

用于创建标识符的语法树节点JCIdent,主要方法如下:

1
2
3
4
5
6
7
8
9
/**
* 用于创建标识符的语法树节点
* @param name 标识符名称
*/
public JCIdent Ident(Name name) {
JCIdent tree = new JCIdent(name, (Symbol)null);
tree.pos = this.pos;
return tree;
}
TreeMaker.Return

用于创建return语句的语法树节点JCReturn,主要方法如下:

1
2
3
4
5
6
7
8
/**
* 用于创建`return`语句的语法树节点
*/
public JCReturn Return(JCExpression ex) {
JCReturn tree = new JCReturn(ex);
tree.pos = this.pos;
return tree;
}
TreeMaker.Apply

用于创建方法调用的语法树节点JCMethodInvocation,主要方法如下:

1
2
3
4
5
6
7
8
9
10
11
/**
* 用于创建方法调用的语法树节点
* @param argsTypes 参数类型列表
* @param fn 方法调用语句
* @param args 方法参数列表
*/
public JCMethodInvocation Apply(List<JCExpression> argsTypes, JCExpression fn, List<JCExpression> args) {
JCMethodInvocation tree = new JCMethodInvocation(argsTypes, fn, args);
tree.pos = this.pos;
return tree;
}
TreeMaker.Select

用于创建域访问/方法访问的语法树节点JCFieldAccess,主要方法如下:

1
2
3
4
5
6
7
8
9
10
/**
* 用于创建域访问/方法访问的语法树节点
* @param selected . 运算符左边的表达式
* @param selector . 运算符右边的表达式
*/
public JCFieldAccess Select(JCExpression selected, Name selector) {
JCFieldAccess tree = new JCFieldAccess(selected, selector, (Symbol)null);
tree.pos = this.pos;
return tree;
}
TreeMaker.Assign

用于创建赋值语句的语法树节点JCAssign,主要方法如下:

1
2
3
4
5
6
7
8
9
10
/**
* 用于创建赋值语句的语法树节点
* @param leftEx 赋值语句左边的表达式
* @param rightEx 赋值语句右边的表达式
*/
public JCAssign Assign(JCExpression leftEx, JCExpression rightEx) {
JCAssign tree = new JCAssign(leftEx, rightEx);
tree.pos = this.pos;
return tree;
}
TreeMaker.Exec

用于创建可执行语句的语法树节点JCExpressionStatement,主要方法如下:

1
2
3
4
5
6
7
8
/**
* 用于创建可执行语句的语法树节点
*/
public JCExpressionStatement Exec(JCExpression ex) {
JCExpressionStatement tree = new JCExpressionStatement(ex);
tree.pos = this.pos;
return tree;
}

TreeMaker.ApplyTreeMaker.Assign需要在外面包一层TreeMaker.Exec来获取一个可执行语句JCExpressionStatement

TreeMaker.Block

用于创建组合语句的语法树节点JCExpressionStatement,主要方法如下:

1
2
3
4
5
6
7
8
9
10
/**
* 用于创建组合语句的语法树节点
* @param flags 访问标志
* @param statements 语句列表
*/
public JCBlock Block(long flags, List<JCStatement> statements) {
JCBlock tree = new JCBlock(flags, statements);
tree.pos = this.pos;
return tree;
}
TreeMaker.MethodDef

用于创建方法定义的语法树节点JCMethodDecl,主要方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 用于创建方法定义的语法树节点
* @param modifiers 访问标志,可通过treeMaker.Modifiers创建
* @param name 方法名
* @param returnType 返回类型
* @param typeParameters 泛型参数列表
* @param params 参数列表
* @param throwns 异常声明列表
* @param methodBody 方法体
* @param defaultValue 默认方法
*/
public JCMethodDecl MethodDef(JCModifiers modifiers,
Name name,
JCExpression returnType,
List<JCTypeParameter> typeParameters,
List<JCVariableDecl> params,
List<JCExpression> throwns,
JCBlock methodBody,
JCExpression defaultValue) {
return this.MethodDef(modifiers, name, returnType, typeParameters, (JCVariableDecl)null, params, throwns, methodBody, defaultValue);
}

public JCMethodDecl MethodDef(JCModifiers modifiers,
Name name,
JCExpression returnType,
List<JCTypeParameter> typeParameters,
JCVariableDecl variable,
List<JCVariableDecl> params,
List<JCExpression> throwns,
JCBlock methodBody,
JCExpression defaultValue) {
JCMethodDecl tree = new JCMethodDecl(modifiers, name, returnType, typeParameters, variable, params, throwns, methodBody, defaultValue, (MethodSymbol)null);
tree.pos = this.pos;
return tree;
}

process执行

下面详细介绍process方法中的具体处理逻辑:

首先要知道process方法如果返回了true,那么Javac编译器会从编译期的第二阶段回到第一阶段:即重新回到解析与填充符号表阶段。

处理逻辑:如果!roundEnv.processingOver()还未process完成,则找到所有@GetterAndSetter注解标注的类,依次遍历获得每个类的语法树,然后交给自定义的org.sunchaser.lombok.impl.process.GetterAndSetterTreeTranslator类进行处理,全部处理完成后返回true以便回到编译的第一阶段。

下面是GetterAndSetterTreeTranslator类中具体操作语法树插入gettersetter方法的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

import static com.sun.tools.javac.tree.JCTree.*;

/**
* AST树访问处理器
* @author sunchaserlilu@didiglobal.com
* @since JDK8 2020/12/17
*/
public class GetterAndSetterTreeTranslator extends TreeTranslator {

/**
* 构造JCTree的工具类
*/
private final TreeMaker treeMaker;

/**
* 名字处理工具类
*/
private final Names names;

/**
* 编译期的日志打印工具类
*/
private final Messager messager;

public GetterAndSetterTreeTranslator(TreeMaker treeMaker, Names names, Messager messager) {
this.treeMaker = treeMaker;
this.names = names;
this.messager = messager;
}

/**
* 访问到类定义时的处理
* @param jcClassDecl 类定义的抽象语法树节点
*/
@Override
public void visitClassDef(JCClassDecl jcClassDecl) {
super.visitClassDef(jcClassDecl);
jcClassDecl.defs.forEach(def -> {
if (def.getKind().equals(Kind.VARIABLE)) {
messager.printMessage(Diagnostic.Kind.NOTE,def + "----processed");
// 插入getter方法
jcClassDecl.defs = jcClassDecl.defs.prepend(createGetterMethod((JCVariableDecl) def));
// 插入setter方法
jcClassDecl.defs = jcClassDecl.defs.prepend(createSetterMethod((JCVariableDecl) def));
}
});
}

/**
* 创建getter方法的语法树节点
* @param def 变量节点
* @return getter方法的语法树节点
*/
private JCTree createGetterMethod(JCVariableDecl def) {
return treeMaker.MethodDef(
// 访问修饰符
treeMaker.Modifiers(Flags.PUBLIC),
// 方法名
names.fromString("get" + this.toFirstUpperCase(def.getName().toString())),
// 方法返回类型
(JCExpression) def.getType(),
// 泛型参数
List.nil(),
// 方法参数列表
List.nil(),
// throw表达式
List.nil(),
// 方法体
treeMaker.Block(0L,List.of(
treeMaker.Return(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")),
names.fromString(def.getName().toString())
)
)
)),
null
);
}

/**
* 创建setter方法的语法树节点
* @param def 变量节点
* @return setter方法的语法树节点
*/
private JCTree createSetterMethod(JCVariableDecl def) {
// 构造方法入参
JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), def.getName(), def.vartype, null);
// 构造 this.def = def
JCFieldAccess thisX = treeMaker.Select(treeMaker.Ident(names.fromString("this")), def.getName());
JCAssign assign = treeMaker.Assign(thisX, treeMaker.Ident(def.getName()));
// 构造方法体
JCBlock methodBody = treeMaker.Block(0L, List.of(treeMaker.Exec(assign)));
// 构造方法返回类型
JCExpression returnType = treeMaker.Type(new Type.JCVoidType());
// 构造setter方法
return treeMaker.MethodDef(
// 访问修饰符
treeMaker.Modifiers(Flags.PUBLIC),
// 方法名
names.fromString("set" + this.toFirstUpperCase(def.getName().toString())),
// 方法返回类型
returnType,
// 泛型参数
List.nil(),
// 方法参数列表
List.of(param),
// throw表达式
List.nil(),
// 方法体
methodBody,
// 默认值
null
);
}

/**
* 工具方法:将字符串首位转为大写
* @param str 源字符串
* @return 首位大写的字符串
*/
private String toFirstUpperCase(String str) {
char[] charArray = str.toCharArray();
charArray[0] -= 32;
return String.valueOf(charArray);
}
}

至此,一个简易Lombok的功能就实现了,但是还无法正常使用,我们需要把我们的插入注解处理器类GetterAndSetterProcessor使用SPI的形式提供给外部使用。

SPI实现

实现方式一:

手动在resources目录下创建META-INF/services目录,然后在services目录下创建javax.annotation.processing.Processor文件,文件内容为插入注解处理器类GetterAndSetterProcessor的全限定类名。

实现方式二:

使用google提供的auto-service框架,该框架也是基于插入注解处理器实现的。其maven坐标为:

1
2
3
4
5
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc7</version>
</dependency>

然后在我们自定义的插入注解处理器类GetterAndSetterProcessor上打上auto-service框架提供的@AutoService(Processor.class)注解,在编译后即自动生成target/classes/META-INF/services/javax.annotation.processing.Processor文件。

测试

我们无法在插入注解处理器类GetterAndSetterProcessor所在的maven模块进行测试,因为该处理器是使用SPI对外提供服务的,所以我们需要新建一个maven子模块,然后引入插入注解处理器GetterAndSetterProcessor类所在的maven模块的坐标,然后才可以使用我们自定义的注解,简单测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.sunchaser.lombok.impl.annotation.GetterAndSetter;

/**
* 注意此类跟GetterAndSetterProcessor不在同一个maven模块中
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2020/12/18
*/
@GetterAndSetter
public class ChainSetterTest {
private String name;
public static void main(String[] args) {

}
}

运行main方法即可。

可看到Build Output中有如下输出:

1
2
3
java: roundEnv --->[errorRaised=false, rootElements=[com.sunchaser.lomboktest.ChainSetterTest], processingOver=false]
java: private String name----processed
java: roundEnv --->[errorRaised=false, rootElements=[], processingOver=true]

打开target/classes/com/sunchaser/lomboktest/ChainSetterTest.class字节码文件,可看到已生成字段namegetNamesetName方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sunchaser.lomboktest;

public class ChainSetterTest {
private String name;

public void setName(String name) {
this.name = name;
}

public String getName() {
return this.name;
}

public ChainSetterTest() {
}

public static void main(String[] args) {
}
}

总结

至此,我们就基本实现了一个简易的Lombok

但是我留下了一个疑问,setter方法如何生成链式的,貌似我没有找到对应的APILombok是通过另一个注解@Accessors(chain = true)来实现链式setter方法的,为什么它不直接在@Setter注解中设置一个属性用来控制生成的setter方法是否为链式的?看Lombok的源码并未看懂这部分的实现,网上也没有找到对应资料,如果有同行研究过这个问题可留言进行交流,十分感谢!

作者

SunChaser

发布于

2020-12-19

更新于

2020-12-19

许可协议

评论