简介: 在写Java代码的时候,最烦写setter/getter办法,自从有了Lombok插件不必再写那些办法之后,感觉再也回不去了,那你们是否好奇过Lombok是怎么把setter/getter办法给你加上去的呢?有的同学说咱们Java引入Lombok之后会净化依赖包,那咱们可不可以本人写一个工具来代替Lombok呢?

作者 | 王再军(曦峰)起源 | 阿里开发者公众号序言在写Java代码的时候,最烦写setter/getter办法,自从有了Lombok插件不必再写那些办法之后,感觉再也回不去了,那你们是否好奇过Lombok是怎么把setter/getter办法给你加上去的呢?有的同学说咱们Java引入Lombok之后会净化依赖包,那咱们可不可以本人写一个工具来代替Lombok呢?知识点Java编译过程理解Lombok原理理解插入式注解处理器剖析序言提到的问题其实都是同一个问题,就是如何去获取和批改Java源代码?要答复这个问题,咱们须要答复这几个问题:Java编译器是如何解析Java源代码的?编译器编译源代码都有哪些步骤?咱们在编译器工作的时候,怎么能力去减少内容或者是进行代码剖析?心愿大家看完本文可能本人写一个繁难的Lombok工具。答复如何解析源代码其实从咱们的代码到被编译,中距离了一个数据结构,叫做AST(形象树)。具体的模式,能够查看上面的图片。左边的便是AST的数据结构了。 

 代码编译都有哪些步骤整个编译过程大抵如下: 

  图片来自openjdk 1、初始化插入注解处理器2、解析与填充符号表过程a.词法剖析、语法分析。将源代码的字符流转变为标记汇合,结构出形象语法树。b.填充符号表。产生符号地址和符号信息。3、插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。前面我会给大家带来两个此方面的实用实战例子。4、剖析与字节码生成过程a.标注查看。对语法的动态信息查看。b.数据流及控制流剖析。对程序动静运行过程进行查看。c.解语法糖。将简化代码编写的语法糖还原为原有的模式。d.字节码生成。将后面各个步骤所生成的信息转化成为字节码。咱们晓得了下面的实践之后,接下来咱们进行实战。带着大家一起去批改AST(形象树)。增加本人的代码。实战如何本人实现一个主动增加Setter/Getter的工具首先,咱们创立一个本人的注解。@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于润饰类
public @interface MySetterGetter {
}
创立一个须要生成setter/getter办法的实体类@MySetterGetter // 打上咱们的注解
public class Test {

private String wzj;

}
接下来就来看一看如何来生成咱们想要的字符串。整体代码如下:@SupportedAnnotationTypes("com.study.practice.nameChecker.MySetterGetter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MySetterGetterProcessor extends AbstractProcessor {

// 次要是输入信息private Messager messager;private JavacTrees javacTrees;private TreeMaker treeMaker;private Names names;@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {    super.init(processingEnv);    this.messager = processingEnv.getMessager();    this.javacTrees = JavacTrees.instance(processingEnv);    Context context = ((JavacProcessingEnvironment)processingEnv).getContext();    this.treeMaker = TreeMaker.instance(context);    this.names = Names.instance(context);}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {    // 拿到被注解标注的所有的类    Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MySetterGetter.class);    elementsAnnotatedWith.forEach(element -> {        // 失去类的形象树结构        JCTree tree = javacTrees.getTree(element);        // 遍历类,对类进行批改        tree.accept(new TreeTranslator(){            @Override            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {                List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();                // 在形象树中找出所有的变量                for(JCTree jcTree: jcClassDecl.defs){                    if (jcTree.getKind().equals(Tree.Kind.VARIABLE)){                        JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl)jcTree;                        jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);                    }                }                                // 对于变量进行生成办法的操作                for (JCTree.JCVariableDecl jcVariableDecl : jcVariableDeclList) {                    messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");                    jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));                    jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));                }    // 生成返回对象    JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());    return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewSetterMethodName(jcVariableDecl.getName()), methodType, List.nil(), parameters, List.nil(), block, null);}/** * 生成 getter 办法 * @param jcVariableDecl * @return */private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl){    ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();    // 生成表达式    JCTree.JCReturn aReturn = treeMaker.Return(treeMaker.Ident(jcVariableDecl.getName()));    statements.append(aReturn);    JCTree.JCBlock block = treeMaker.Block(0, statements.toList());    // 无入参    // 生成返回对象    JCTree.JCExpression returnType = treeMaker.Type(jcVariableDecl.getType().type);    return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewGetterMethodName(jcVariableDecl.getName()), returnType, List.nil(), List.nil(), List.nil(), block, null);}/** * 拼装Setter办法名称字符串 * @param name * @return */private Name getNewSetterMethodName(Name name) {    String s = name.toString();    return names.fromString("set" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));}/** * 拼装 Getter 办法名称的字符串 * @param name * @return */private Name getNewGetterMethodName(Name name) {    String s = name.toString();    return names.fromString("get" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));}/** * 生成表达式 * @param lhs * @param rhs * @return */private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {    return treeMaker.Exec(            treeMaker.Assign(lhs, rhs)    );}

}代码有点多,咱们逐个拆解阐明:上面这是整个代码构造的脑图,前面的解说会基于这个程序。 

 a. 注解@SupportedAnnotationTypes 示意咱们须要监听的注解,比方咱们之前定义的 @MySetterGetter。@SupportedSourceVersion 示意咱们想要对什么版本的Java源代码进行解决。b. 父类AbstractProcessor是本次的外围类,编译器在编译的时候会扫描此类的子类。其中有一个子类必须实现的外围办法 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv),此办法如果是返回为true就阐明编译的那个类形象树的构造又变动,须要从新进行词法剖析和语法分析(能够查看下面提到的那个编译流程图)。如果返回的是false就阐明没有变动。c. process办法次要的操作逻辑是:1、拿到所有被咱们MySetterGetter标注的类。2、遍历所有的类,生成类的形象树结构。3、对类进行操作:a.找到类中所有的变量。b.对变量进行生成Set和Get办法。4、返回 true,阐明类构造变了,须要从新解析。如果是false阐明没有变,不必从新解析。d. 操作JCTree树次要是在操作形象树,能够查看文末附件中的文章进行学习。e. 办法名称拼接这一块儿和字符串拼接没啥区别,用过反射的同学应该也都分明这个操作了。到此为止,咱们就曾经介绍完了Lombok的原理。怎么样是不是很简略。接下来,就让咱们把它运行起来,投入到实战之中。f. 运行最初来看一下如何正确的运行这个咱们写的工具。1.环境我的零碎环境是 macOs Monterey;java版本是openjdk version "1.8.0_302"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM (Temurin)(build 25.302-b08, mixed mode)
2.编译processor在你寄存 MySetterGetter 和 MySetterGetterProcessor 两个类的目录下进行编译。javac -cp $JAVA_HOME/lib/tools.jar MySetterGetter.java MySetterGetterProcessor.java
执行胜利后会呈现这三个class文件。 

 3.申明插入式注解处理器 

 在你的工程的resources上面创立一个包,名称为:META-INFO.services而后创立一个文件,名称为:javax.annotation.processing.Processor将你的注解处理器的地址填入,我的配置是这样的:com.study.practice.nameChecker.MySetterGetterProcessor4.用咱们的工具去编译指标类比方咱们本次是要编译那个test.java。它的内容再回顾一下:@MySetterGetter // 打上咱们的注解
public class Test {

private String wzj;

}
而后咱们就去编译它(留神类后面的门路。这个你们得换成本人的工程目录。)javac -processor com.study.practice.nameChecker.MySetterGetterProcessor com/study/practice/nameChecker/Test.java
执行之后如果没有批改我的代码的话会打印这几个字符串:process 1
process 2
注: wzj has been processed
process 1
最初会生成Test.class文件。 

 5.成绩最初的class文件解析进去就是这个样子的。如下图所示: 

 看到Setter/Getter办法就阐明咱们曾经功败垂成了!是不是很简略。到此为止,咱们就学会了如何本人写一个属于本人的繁难Lombok的插件了。附件treemarker 的介绍:http://www.docjar.com/docs/ap... ModelScope开源模型社区评测征集令 ModelScope开源模型社区评测专场重磅来袭,公布你的评测,收费应用模型库搭建属于你的利用,有机会取得AirPods和阿里云定制礼品,更有多重福利,点击这里查看流动详情。原文链接:https://click.aliyun.com/m/10...本文为阿里云原创内容,未经容许不得转载。