前言写Java代码的时候,最烦的就是写setter/getter方法。既然不用再用Lombok插件写那些方法了,感觉回不去了。你有没有想过Lombok是如何使用setter/getter方法的?getter方法给你增加了什么?有同学说,我们把Lombok引入Java后,会污染依赖包,那可不可以自己写一个工具来替代Lombok呢?知识点Java编译过程了解Lombok原理了解插件注解处理器分析前言中提到的问题其实都是同一个问题,即如何获取和修改Java源码?要回答这个问题,我们需要回答这些问题:Java编译器是如何解析Java源代码的?编译器编译源代码的步骤是什么?我们在做编译器的时候如何添加内容或者进行代码分析呢?希望看完本文后,您可以自己编写一个简单的Lombok工具。要回答如何解析源码,其实从我们的代码到被编译,都有一个数据结构叫AST(abstracttree)。有关详细信息,请参见下图。右边是AST的数据结构。代码编译的步骤是什么?整个编译过程大致如下:图片来自openjdk1,初始化插入注解处理器2,解析填充符号表的过程a。词法分析,句法分析。将源代码的字符流转化为一组token,构建抽象语法树。b.填充符号表。生成符号地址和符号信息。3、插件注解处理器的注解处理过程:插件注解处理器的执行阶段。后面我会给大家举两个这方面的实际例子。4.分析和字节码生成过程a.注释检查。语法的静态信息检查。b.数据流和控制流分析。检查程序的动态运行过程。C。分解语法糖。将简化代码编写的语法糖恢复到原来的形式。d.字节码生成。将前面步骤生成的信息转换成字节码。知道了上面的理论之后,我们就进入实战。带大家修改一下AST(抽象树)。添加您自己的代码。实践中如何实现一个自动添加Setter/Getter的工具?首先,我们创建自己的注释。@Retention(RetentionPolicy.SOURCE)//注解只在源码中保留@Target(ElementType.TYPE)//用于修改类public@interfaceMySetterGetter{}创建需要生成setter/getter方法的实体类@MySetterGetter//MarkOurannotationpublicclassTest{privateStringwzj;}下面我们来看看如何生成我们想要的字符串。整体代码如下:@SupportedAnnotationTypes("com.study.practice.nameChecker.MySetterGetter")@SupportedSourceVersion(SourceVersion.RELEASE_8)publicclassMySetterGetterProcessorextendsAbstractProcessor{//主要是输出信息privateMessagermessager;私有JavacTreesjavacTrees;私人TreeMakertreeMaker;私有名称;@Overridepublicsynchronizedvoidinit(ProcessingEnvironmentprocessingEnv){super.init(processingEnv);this.messager=processingEnv.getMessager();this.javacTrees=JavacTrees.instance(processingEnv);上下文context=((JavacProcessingEnvironment)processingEnv).getContext();this.treeMaker=TreeMaker.instance(context);this.names=Names.instance(context);}@Overridepublicbooleanprocess(Setannotations,RoundEnvironmentroundEnv){//拿到被注解标记的所有的类SetelementsAnnotatedWith=roundEnv.getElementsAnnotatedWith(MySetterGetter.class);elementsAnnotatedWith.forEach(element->{//获取类JCTree的抽象树结构tree=javacTrees.getTree(element);//遍历类并修改类tree.accept(newTreeTranslator(){@OverridepublicvoidvisitClassDef(JCTree.JCClassDecljcClassDecl){ListjcVariableDeclList=List.nil();//查找抽象树中的所有变量for(JCTreejcTree:jcClassDecl.defs){if(jcTree.getKind().equals(Tree.Kind.VARIABLE)){JCTree.JCVariableDecljcVariableDecl=(JCTree.JCVariableDecl)jcTree;jcVariableDeclList=jcVariableDeclList.append(jcVariableDecl);}}//为变量生成方法(JCTree.JCVariableDecljcVariableDecl:jcVariableDeclList){messager.printMessage(Diagnostic.Kind.NOTE,jcVariableDecl.getName()+"已被处理");jcClassDecl.defs=jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));jcClassDecl.defs=jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));}//生成返回对象JCTree.JCExpressionmethodType=treeMaker.Type(newType.JCVoidType());返回treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),getNewSetterMethodName(jcVariableDecl.getName()),methodType,List.nil(),parameters,List.nil(),block,null);}/***生成getter方法*@paramjcVariableDecl*@returnprivateJCTree.JCMethodDeclmakeGetterMethodDecl(JCTree.JCVariableDecljcVariableDecl){ListBufferstatements=newListBuffer<>();//生成表达式JCTree.JCReturnaReturn=treeMaker.Return(treeMaker.Ident(jcVariableDecl.getName()));statements.append(aReturn);JCTree.JCBlock块=treeMaker.Block(0,statements.toList());//无输入参数//生成返回对象JCTree.JCExpressionreturnType=treeMaker.Type(jcVariableDecl.getType().type);返回treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),getNewGetterMethodName(jcVariableDecl.getName()),returnType,List.nil(),List.nil(),List.nil(),block,null);}/***AssemblySetter方法名字符串*@paramname*@returnprivateNamegetNewSetterMethodName(Namename){Strings=name.toString();返回names.fromString("set"+s.substring(0,1).toUpperCase()+s.substring(1,name.length()));}/***StringtoassembleGettermethodname*@paramname*@returnprivateNamegetNewGetterMethodName(Namename){Strings=name.toString();返回names.fromString("get"+s.substring(0,1).toUpperCase()+s.substring(1,name.length()));}/***生成表达式*@paramlhs*@paramrhs*@returnprivateJCTree.JCExpressionStatementmakeAssignment(JCTree.JCExpressionlhs,JCTree.JCExpressionrhs){returntreeMaker.Exec(treeMaker.Assign(lhs,rhs));}}代码有很多,我们一一拆解解释:下面是整个代码结构的脑图,后面的解释都会按照这个顺序a。@SupportedAnnotationTypes注解表示我们需要监控的注解,比如我们之前定义的@MySetterGetter。@SupportedSourceVersion指示我们要处理的Java源代码的版本。b。父类AbstractProcessor是本项目的核心类,编译器在编译时会扫描该类的子类。有一个核心方法publicbooleanprocess(Set注解,RoundEnvironmentroundEnv)必须由子类实现。如果该方法返回true,说明编译类的抽象树结构发生了变化,需要重新执行词法方法。分析和语法分析(可以查看上面提到的编译流程图)。如果它返回false,则表示没有变化。C。process方法的主要运行逻辑是:1.获取我们MySetterGetter标记的所有类。2.遍历所有类,生成类的抽象树结构。3.运营班级:找到类中的所有变量。b.为变量创建Set和Get方法。4、返回true,说明类结构发生变化,需要重新解析。如果为false,说明没有变化,不需要重新解析。d.操作JCTree树主要是操作抽象树,可以查看文末附件中的文章进行学习。e.方法名拼接和字符串拼接没有区别。用过反射的同学应该也知道这个操作。至此,我们已经介绍了Lombok的原理。怎么不是很简单。下面让我们来运行一下,投入到实战中。F。运行最后,让我们看看如何正确运行我们编写的工具。1.环境我的系统环境是macOsMonterey;java版本是openjdk版本“1.8.0_302”OpenJDKRuntimeEnvironment(Temurin)(build1.8.0_302-b08)OpenJDK64-BitServerVM(Temurin)(build25.302-b08,mixedmode)2.编译处理器编译在存储两个类MySetterGetter和MySetterGetterProcessor的目录。javac-cp$JAVA_HOME/lib/tools.jarMySetterGetter.javaMySetterGetterProcessor.java执行成功后,会出现这三个类文件。3.声明插件annotationprocessor在你项目的资源下创建一个包命名为:META-INFO.services然后创建一个文件命名为:javax.annotation.processing.Processor将你的annotationprocessor地址填入,我的配置是像这样:com.study.practice.nameChecker.MySetterGetterProcessor4.使用我们的工具编译目标类。比如我们这次要编译test.java。再次查看其内容:@MySetterGetter//添加我们的注解publicclassTest{privateStringwzj;}然后我们编译它(注意类前面的路径,您必须将其替换为您自己的项目目录。)javac-Processorcom.study.practice.nameChecker.MySetterGetterProcessorcom/study/practice/nameChecker/Test.java执行后如果我的代码没有被修改,会打印这些字符串:process1process2注意:wzj已经被processedprocess1finallyGenerateTest.class文件。5.结果就是最后的class文件是这样解析的。如下图所示:看到Setter/Getter方法就说明大功告成了!是不是很简单。到目前为止,我们已经学会了如何编写我们自己的简单Lombok插件。附treemarker介绍:http://www.docjar.com/docs/api/com/sun/tools/javac/tree/TreeMaker.html