编译时注解

news/2024/7/11 1:02:23 标签: java, 编译器, annotations, javac, processing

很早之前我便会了使用编译时注解生成新的文件,类似安卓中黄油刀等,但是我的目标不是生成新的类,而是在编译时修改字节码做出类似Lombok那样的插件最近抽空找了一些资料终于写出了自己的编译时字节码修改注解

参考:https://liuyehcf.github.io/2018/02/02/Java-JSR-269-%E6%8F%92%E5%85%A5%E5%BC%8F%E6%B3%A8%E8%A7%A3%E5%A4%84%E7%90%86%E5%99%A8/
关于对应的mavan配置我前面写过,网上也有一大堆,这里就不再重复了
注意编写前必须先引入jdk自带的tools.jar文件

<dependencies>
        <dependency>
            <groupId>jdk.tools</groupId>
            <artifactId>jdk.tools</artifactId>
            <version>${java.version}</version>
            <scope>system</scope>
            <systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath>
        </dependency>
    </dependencies>

NoArgsConstructorProcessor

java">@SupportedAnnotationTypes("annotations.NoArgsConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class NoArgsConstructorProcessor extends AbstractProcessor {

    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        javacTrees=JavacTrees.instance(context);//把 Element 转为 JCTree
        treeMaker=TreeMaker.instance(context);// 构造语法树节点
        names=Names.instance(context);// 用于产生标识符
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(NoArgsConstructor.class);
        elements.forEach((Consumer<Element>) element -> javacTrees.getTree(element).accept(new TreeTranslator(){
            @Override//采用访问者模式  访问类的定义
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                super.visitClassDef(jcClassDecl);
                if(!hasNoArgsConstructor(jcClassDecl)){
                    // 注意  List每次都会返回新对象
                    jcClassDecl.defs=jcClassDecl.defs.append(createNoArgsConstructor(jcClassDecl));
                }
            }
        }));
        return true;
    }

    private JCTree.JCMethodDecl createNoArgsConstructor(JCTree.JCClassDecl jcClassDecl) {
        treeMaker.pos=jcClassDecl.pos;//注意 偏移必须对准
        JCTree.JCBlock block=treeMaker.Block(0, List.nil());//空代码块
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),//访问标识
                names.fromString("<init>"),//方法名
                treeMaker.TypeIdent(TypeTag.VOID),//返回值类型
                List.nil(),//泛型列表
                List.nil(),//参数列表
                List.nil(),//抛出的异常
                block,//代码块
                null);
    }

    // 判断是否已经有默认的空参构造器
    private boolean hasNoArgsConstructor(JCTree.JCClassDecl jcClassDecl) {
        for (JCTree jcTree:jcClassDecl.defs){
            if(jcTree.getKind().equals(Tree.Kind.METHOD)){
                JCTree.JCMethodDecl methodDecl= (JCTree.JCMethodDecl) jcTree;
                if(methodDecl.getName().toString().equals("<init>")&&methodDecl.getParameters().isEmpty()){
                    return true;
                }
            }
        }
        return false;
    }

}

AllArgsConstructorProcessor

java">@SupportedAnnotationTypes("annotations.AllArgsConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AllArgsConstructorProcessor extends AbstractProcessor {

    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        javacTrees=JavacTrees.instance(context);
        treeMaker=TreeMaker.instance(context);
        names=Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AllArgsConstructor.class);
        elements.forEach(new Consumer<Element>() {
            @Override
            public void accept(Element element) {
                javacTrees.getTree(element).accept(new TreeTranslator(){
                    @Override
                    public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                        super.visitClassDef(jcClassDecl);
                        jcClassDecl.defs=jcClassDecl.defs.append(createAllArgsConstructor(jcClassDecl));
                    }
                });
            }
        });
        return true;
    }

    private JCTree.JCMethodDecl createAllArgsConstructor(JCTree.JCClassDecl jcClassDecl) {
        treeMaker.pos=jcClassDecl.pos;
        List<JCTree.JCVariableDecl> param=getParam(jcClassDecl);
        // ListBuffer 跟类似  Java原生List  可以直接使用append  也可使用  toList 转为  tools.jar下的List
        ListBuffer<JCTree.JCStatement> buffer=new ListBuffer<>();
        param.forEach(variableDecl -> buffer.append(
                treeMaker.Exec(treeMaker.Assign(//赋值操作
                treeMaker.Select(treeMaker.Ident(names.fromString("this")),variableDecl.name),// this.<name>
                treeMaker.Ident(variableDecl.name)))));//<param.name>
        JCTree.JCBlock block = treeMaker.Block(0, buffer.toList());
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),names.fromString("<init>"),
                treeMaker.TypeIdent(TypeTag.VOID), List.nil(),param, List.nil(),block,null);
    }

    // 获取所有非静态Final变量
    private List<JCTree.JCVariableDecl> getParam(JCTree.JCClassDecl jcClassDecl) {
        ListBuffer<JCTree.JCVariableDecl> buffer = new ListBuffer<>();
        for (JCTree tree:jcClassDecl.defs){
            if(tree.getKind().equals(Tree.Kind.VARIABLE)){
                JCTree.JCVariableDecl variableDecl= (JCTree.JCVariableDecl) tree;
                Set<Modifier> flags = variableDecl.mods.getFlags();
                if(!flags.contains(Modifier.FINAL)&&!flags.contains(Modifier.STATIC)){
                    buffer.append(treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),variableDecl.name,
                            variableDecl.vartype,null));
                }
            }
        }
        return buffer.toList();
    }

}

DataProcessor

java">@SupportedAnnotationTypes("annotations.Data")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DataProcessor extends AbstractProcessor {

    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        javacTrees=JavacTrees.instance(context);
        treeMaker=TreeMaker.instance(context);
        names=Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        roundEnv.getElementsAnnotatedWith(Data.class).forEach(new Consumer<Element>() {
            @Override
            public void accept(Element element) {
                javacTrees.getTree(element).accept(new TreeTranslator(){
                    @Override
                    public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                        super.visitClassDef(jcClassDecl);
                        List<JCTree.JCVariableDecl> param=getParam(jcClassDecl);
                        param.forEach(variableDecl -> {
                            //  添加  getter  setter
                            jcClassDecl.defs=jcClassDecl.defs.append(createGetter(variableDecl));
                            jcClassDecl.defs=jcClassDecl.defs.append(createSetter(variableDecl));
                        });
                    }
                });
            }
        });
        return true;
    }

    private JCTree.JCMethodDecl createSetter(JCTree.JCVariableDecl variableDecl) {
        treeMaker.pos=variableDecl.pos;
        JCTree.JCBlock block = treeMaker.Block(0, List.of(treeMaker.Exec(treeMaker.Assign(
                treeMaker.Select(treeMaker.Ident(names.fromString("this")), variableDecl.name),
                treeMaker.Ident(variableDecl.name)))));
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
                names.fromString(toSetter(variableDecl.getName().toString())),treeMaker.TypeIdent(TypeTag.VOID),
                List.nil(),List.of(variableDecl),List.nil(),block,null);
    }

    private String toSetter(String name) {
        return "set"+name.substring(0,1).toUpperCase()+name.substring(1);
    }

    private JCTree.JCMethodDecl createGetter(JCTree.JCVariableDecl variableDecl) {
        treeMaker.pos=variableDecl.pos;
        JCTree.JCStatement statement=treeMaker.Return(// return
                treeMaker.Select(treeMaker.Ident(names.fromString("this")),variableDecl.name));// this.<name>
        JCTree.JCBlock block = treeMaker.Block(0, List.of(statement));
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
                names.fromString(toGetter(variableDecl.getName().toString())),variableDecl.vartype,List.nil(),
                List.nil(),List.nil(),block,null);
    }

    private String toGetter(String name) {
        return "get"+name.substring(0,1).toUpperCase()+name.substring(1);
    }

    private List<JCTree.JCVariableDecl> getParam(JCTree.JCClassDecl jcClassDecl) {
        ListBuffer<JCTree.JCVariableDecl> buffer = new ListBuffer<>();
        for (JCTree tree:jcClassDecl.defs){
            if(tree.getKind().equals(Tree.Kind.VARIABLE)){
                JCTree.JCVariableDecl variableDecl= (JCTree.JCVariableDecl) tree;
                Set<Modifier> flags = variableDecl.mods.getFlags();
                if(!flags.contains(Modifier.FINAL)&&!flags.contains(Modifier.STATIC)){
                    buffer.append(treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),variableDecl.name,
                            variableDecl.vartype,null));
                }
            }
        }
        return buffer.toList();
    }

}

学习笔记

文档地址

https://liuyehcf.github.io/2018/02/02/Java-JSR-269-%E6%8F%92%E5%85%A5%E5%BC%8F%E6%B3%A8%E8%A7%A3%E5%A4%84%E7%90%86%E5%99%A8/

准备

必须导入jdk的tools.jar文件

<dependency>
    <groupId>jdk.tools</groupId>
    <artifactId>jdk.tools</artifactId>
    <version>${java.version}</version>
    <scope>system</scope>
    <systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>

添加打包插件防止死循环

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <!-- Disable annotation processing for ourselves.-->
                <compilerArgument>-proc:none</compilerArgument>
            </configuration>
        </plugin>
    </plugins>
</build>

javaxannotationprocessingProcessor_271">在MATE-INF/services下的javax.annotation.processing.Processor注册注解处理器

基本概念

Java编译过程

解析与填充符号表过程

插入式注解处理器的注解处理过程

分析与字节码生成过程

JCTree

基类,属性**pos**指定在语法树中的位置,因此各个语法组件无法直接new,需要使用TreeMake生成

常见子类

  1. JCStatement:声明 语法树节点,常见的子类如下
    • JCBlock:语句块语法树节点
    • JCReturn:return语句语法树节点
    • JCClassDecl:类定义语法树节点
    • JCVariableDecl:字段/变量定义语法树节点
  2. JCMethodDecl:方法定义语法树节点
  3. JCModifiers:访问标志语法树节点
  4. JCExpression:表达式 语法树节点,常见的子类如下
    • JCAssign:赋值语句语法树节点
    • JCIdent:标识符语法树节点,可以是变量,类型,关键字等等

TreeMaker

用于创建语法树节点,会为创建的节点设置pos,必须使用上下文相关的不能直接new

TreeMaker.Modifiers

访问标识符 flags:访问标志 annotations:注解列表

TreeMaker.ClassDef

用于定义类 mods:访问标志 name:类名 typarams:泛型参数列表 extending:父类 implementing:接口列表 defs:类定义的详细语句,包括字段,方法定义等等

TreeMaker.MethodDef

用于定义方法 mods:访问标志 name:方法名 restype:返回类型 typarams:泛型参数列表 params:参数列表 thrown:异常声明列表 body:方法体 defaultValue:默认方法(可能是interface中的那个default) m:方法符号 mtype:方法类型。包含多种类型,泛型参数类型、方法参数类型,异常参数类型、返回参数类型

TreeMaker.VarDef

用于创建字段或变量 mods:访问标志 vartype:类型 init:初始化语句 v:变量符号

TreeMaker.Ident

用于创建标识符

TreeMaker.Return

用于创建return语句

TreeMaker.Select

获取对象属性 selected:.运算符左边的表达式 selector:.运算符右边的名字

TreeMaker.NewClass

用于创建new语法树 encl:不太明白此参数含义 typeargs:参数类型列表 clazz:待创建对象的类型 args:参数列表 def:类定义

TreeMaker.Apply

方法调用 typeargs:参数类型列表 fn:调用语句 args:参数列表

TreeMaker.Assign

创建赋值语句 lhs:赋值语句左边表达式 rhs:赋值语句右边表达式

TreeMaker.Exec

用于执行可执行语法树 JCExpressionStatement

TreeMaker.Block

用于创建组合语句语法树节点 flags:访问标志 stats:语句列表

javacutilList_352">com.sun.tools.javac.util.List

特殊List每次添加对象会返回新List

javacutilListBuffer_356">com.sun.tools.javac.util.ListBuffer

与Java原生list类似且提供转为List的方法

例子

创建方法

java">treeMaker.MethodDef(
    treeMaker.Modifiers(Flags.PUBLIC), //访问标志
    names.fromString("<init>"), //名字
    treeMaker.TypeIdent(TypeTag.VOID), //返回类型
    List.nil(), //泛型形参列表
    List.nil(), //参数列表
    List.nil(), //异常列表
    jcBlock, //方法体
    null //默认方法(可能是interface中的那个default)
);

方法参数

java">treeMaker.VarDef(
    treeMaker.Modifiers(Flags.PARAMETER), //访问标志。极其坑爹!!!
    prototypeJCVariable.name, //名字
    prototypeJCVariable.vartype, //类型
    null //初始化语句
);

赋值语句

java">treeMaker.Exec(
    treeMaker.Assign(
        treeMaker.Select(
            treeMaker.Ident(names.fromString(THIS)),
            jcVariable.name
        ),
        treeMaker.Ident(jcVariable.name)
    )
)

new对象

java">treeMaker.NewClass(
    null, //尚不清楚含义
    List.nil(), //泛型参数列表
    treeMaker.Ident(builderClassName), //创建的类名
    List.nil(), //参数列表
    null //类定义,估计是用于创建匿名内部类
)

方法调用

java">treeMaker.Exec(
    treeMaker.Apply(
        List.nil(),
        treeMaker.Select(
            treeMaker.Ident(getNameFromString(IDENTIFIER_DATA)),
            jcMethodDecl.getName()
        ),
        List.of(treeMaker.Ident(jcVariableDecl.getName())) //传入的参数集合
    )
)

扩展

Java编译器编译过程

Parser:将符号流映射成一个AST

Enter:找到当前范围的所有定义并转换为符号

Process Annotations:可选,处理编译时注解,不能使用反射操作

Attribute:为生成的AST添加属性

Flow:对数据流进行检查

Desugar:还原去除语法糖

Generate:生成类文件


http://www.niftyadmin.cn/n/657824.html

相关文章

什么是测试用例

1、什么是测试用例 一组由前提条件、输入、执行条件、预期结果等组成&#xff0c;以完成对某个特定需求或者目标测试的数据&#xff0c;体现测试方案、方法、技术和策略的文档 2、为什么要写测试用例 科学有效的对测试步骤进行组织规划&#xff0c;方便管理&#xff0c;记录 3、…

PL/SQL中的 for Update of 应用一例

for update 是为当前的查询加锁。利用这种方式可以大大的提高效率。下面的一个例子中利用有 for update of 的 游标更新数据。当然具体效率的提升情况需要用大数据量的处理来测试才能得出来。 declarecursorgData(var1 varchar2) isselectitem_name,item_name_en,code_value f…

【学习】eclipse自动提示+自动补全

解决代码的自动提示问题&#xff1a; 1、打开 Eclipse -> Window -> Perferences 2、找到Java 下的 Editor 下的 Content Assist , 右边出现的选项中&#xff0c;有一个Auto activation triggers for Java: 会看到只有一个”.”存在。表示&#xff1a;只有输入”.”之…

仿B站web,APP,后台

体验地址 web端&#xff1a;http://82.157.168.147/ 安卓端&#xff1a;http://82.157.168.147:7000/bilibili/phone/app.html 测试账号&#xff1a;17627286393 密码:123456 仅测试使用&#xff0c;推荐使用自己的手机号&#xff0c;否则部分功能部分使用&#xff0c;请不要用…

Vue组件~父子组件之间的通信(传值)

1.父组件向子组件传参 父组件中通过v-bind对要传参数的绑定:(例如) :dataSourceName:是在子组件中要用到的值 "ctpSaveEchartSetting.dataSourceId":是在父组件中要传的值 子组件中的接收通过props(数据值单项绑定的)进行接收:(例如) 注:在props中有的变量就不能再da…

今天要出去玩了

本周六&#xff08;今天&#xff09;要出去玩的事情都已经部里面沸腾好几天了&#xff0c;本来听说这次活动只有党团员可以参加。所以觉得自己这样的已经推出了历史舞台的没有机会去了。可不知道色用了什么办法&#xff0c;把参加人员的范围扩大到了党团员&#xff0b;&#xf…

Java游戏框架编写

自己抽空编写的一个2D游戏框架(也可以说是工具类集吧)&#xff0c;在此记录一下&#xff0c;开发完了顺便写了一个案例判断框架的可用性 项目地址&#xff1a;https://gitee.com/shaokang123/spring-game-starter 框架特点 支持直接使用Tiled设计地图&#xff0c;以name为beanN…

有台服务可以干哪些好玩的事

从我接触服务器已经两年了&#xff0c;中间用服务器干了不少好玩的事&#xff0c;特此记录一下&#xff0c;也可以算是给刚有服务器的同学找点乐子 下面记录一下我用服务器干了什么&#xff0c;没有顺序想到哪说到哪 github大佬搜集的个人服务器超好用的项目 https://github.c…