Annotation Processor

news2024/11/23 12:01:01

Annotation Processor

Processor处理约定

  1. JavaC编译环境获取当前的源文件及类文件, 建立多轮次的处理过程。每一次轮次的处理结果将作为下一轮的输入。当某一轮处理完成后没有产生新的源文件或类文件,触发最后一轮。
  2. Processors 通过getSupportedAnnotationTypes、 getSupportedOptions、 getSupportedSourceVersion 方法确定是否匹配需要处理的Annotation.
  3. Processor的process方法返回值: false表明匹配注解没有被processor正确处理, 对应的注解将继续寻找合适的processor处理, 返回true表明已经被处理,对应的注解在本轮次将不再交给别的processor处理。 Process方法如果抛出未捕获的异常,将中断编译过程。

Processor 接口方法

/**

 *识别和匹配注解的@SupportedOptions配置

 */

Set<String> getSupportedOptions();

/**

 *返回processor匹配的注解类型

 */

Set<String> getSupportedAnnotationTypes();

/**

 *返回process支持的最新源码版本

 */

SourceVersion getSupportedSourceVersion();

/**

 *由执行环境调用对processor进行初始化

 */

void init(ProcessingEnvironment processingEnv);

/**

 *注解处理方法

 */

boolean process(Set<? extends TypeElement> annotations,

                RoundEnvironment roundEnv);

抽象语法树(AST)

Annotation Processor目前所常见的场景就是编译时根据特定注解改造最终生成的类文件, 如lombok。 相应功能离不开JAVA 抽象语法树(AST)的概念及相关接口。AST 是JAVA语法的抽象, 编译过程中源文件的Java语法将解析为AST并提供相应操作接口进行读取和修改。

com.sun.source.tree.Tree接口: 语法树的基础接口, 所有的语法块均实现该接口,提供访问者模式接口:<R, D> R accept(TreeVisitor<R, D> var1, D var2);

com.sun.source.util.Trees类: 语法树工具类, 提供了获取语法树相关Tree对象的方法。使用的实现类JavacTrees

Tree.Kind 枚举: 定义了语法块的类型。如 ANNOTATION(AnnotationTree.class),

JCTree: Tree接口的抽象实现类, 其他具体类型集成实现该类。

TreeTranslator:JCTree的访问者接口实现。 Processor实现中实现该类的子类,覆盖注解目标对应类型的visit方法,如访问类结构的visitClassDef(JCClassDecl jcClassDecl)方法。

Processor示例-样例

考虑一个问题: 如何往一个不能直接修改源码的类里面增加域? 比如接口协议对象作为一个规范,不允许进行源码修改特定类型。但要进行扩展,增加参数。

当然答案有很多,比如 通过替换同名的类, 通过Spring的FactoryBean等扩展点替换掉类定义等。这里说明如何通过Annotation processor的方式实现。

需要做的工作包括:

  1. 定义一个注解,该注解作用在类上, 作用范围是源码。
  2. 在使用该注解的类上增加需要添加的域, 并且通过某种方式指明要讲新的域加入的目标类型。 这里有不同的实现方式,比如可以再注解中直接指明目标类型,示例使用集成的方式说明新的域要加入到父类中,这样保持类型语义的连续性。
  3. 实现Annotation Processor, 对新增了注解的类进行解析,将新增注解类中的域添加到父类中(重新父类的类文件)。

关键代码如下:

@Retention(RetentionPolicy.SOURCE)

@Target({ElementType.TYPE})

public @interface InsertField {

}

public synchronized void init(ProcessingEnvironment processingEnv) {

         //messager用于打印日记

    this.messager = processingEnv.getMessager();

    //获取Trees对象,作为Tree语法块的获取入口

    this.trees = JavacTrees.instance(processingEnv);

}

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    try {

        this.messager.printMessage(Kind.NOTE, "start to process extend POJO");

        //获取打了InsertField注解的类

        Iterator insertFieldClassIterator = roundEnv.getElementsAnnotatedWith(InsertField.class).iterator();

        while(insertFieldClassIterator.hasNext()) {

            Element element = (Element)insertFieldClassIterator.next();

            JCTree jcTree = this.trees.getTree(element);

            jcTree.accept(new TreeTranslator() {

                public void visitClassDef(JCClassDecl jcClassDecl) {

                    try {

                        ClassPool cp = ClassPool.getDefault();

                        //获取其继承的父类,需要将域插入到父类中

                        String targetClassName = ((JCIdent)jcClassDecl.extending).sym.toString();

                        targetClassName = StringUtils.replace(targetClassName, ".", "/")+ ".class";

                        //读入要修改的类

                        InputStream targetClassStream = this.getClass().getClassLoader().getResourceAsStream(targetClassName);

                        if (targetClassStream == null) {

                            this.messager.printMessage(Kind.WARNING, "非标准类, 无法插入");

                        } else {

                            //生成CtClass对象 用于插入新增的域及方法

                            CtClass targetClass = cp.makeClass(targetClassStream);

                            //遍历注解类的定义,获取域

                            List<JCVariableDecl> jcVariableDeclList = List.nil();

                            Iterator defsIterator = jcClassDecl.defs.iterator();

                            while(defsIterator.hasNext()) {

                                JCTree tree = (JCTree)defsIterator.next();

                                if (tree.getKind().equals(com.sun.source.tree.Tree.Kind.VARIABLE)) {

                                    JCVariableDecl jcVariableDecl = (JCVariableDecl)tree;

                                    Set<Modifier> flags = jcVariableDecl.mods.getFlags();

                                    if (!flags.contains(Modifier.FINAL) && !flags.contains(Modifier.STATIC)) {

                                        jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);

                                    }

                                }

                            }

                            var7 = jcVariableDeclList.iterator();

                            while(var7.hasNext()) {

                                JCVariableDecl jcVariableDeclx = (JCVariableDecl)var7.next();

                                ExtendPOJOProcessor.this.messager.printMessage(Kind.NOTE, jcVariableDeclx.getName() + " has been processed");

                                String fieldType = jcVariableDeclx.vartype.type.tsym.toString();

                                String fieldName = (new StringBuffer()).append("private ").append(fieldType).append(" ").append(jcVariableDeclx.getName()).append(";").toString();

                                if (cp.getOrNull(fieldType) == null) {

                                    cp.makeClass(fieldType);

                                }

                                //创建新增的域

                                CtField ctField = CtField.make(fieldName, targetClass);

                                ConstPool constPool = targetClass.getClassFile().getConstPool();                  

                                String recapitalizedName = org.springframework.util.StringUtils.capitalize(ctField.getName());

                                String setterMethodName = "set" + recapitalizedName;

                                String getterMethodName = "get" + recapitalizedName;

                                 //创建新增域的get/set方法

                                CtMethod fieldSetter = CtNewMethod.setter(setterMethodName, ctField);

                                CtMethod fieldGetter = CtNewMethod.getter(getterMethodName, ctField);

                                //设置方法的signature

                                List<Type> types = jcVariableDeclx.vartype.type.getTypeArguments();

                                if (CollectionUtils.isNotEmpty(types)) {

                                    StringBuilder signatureBuilder = (new StringBuilder("L")).append(Descriptor.toJvmName(jcVariableDeclx.vartype.type.tsym.toString())).append("<");

                                    Iterator typeIterator = types.iterator();

                                    while(typeIterator.hasNext()) {

                                        Type type = (Type)typeIterator.next();

                                        signatureBuilder.append(Descriptor.of(type.toString()));

                                    }

                                    signatureBuilder.append(">;");

                                    ctField.setGenericSignature(signatureBuilder.toString());

                                    fieldSetter.setGenericSignature("(" + signatureBuilder + ")V");

                                    fieldGetter.setGenericSignature("()" + signatureBuilder);

                                }

                                //将域加入targetClass

                                targetClass.addField(ctField);

                                //将域的getter方法加入targetClass

                                targetClass.addMethod(fieldSetter);

                                //将域的setter方法加入targetClass

                                targetClass.addMethod(fieldGetter);

                            }

                            //重写要插入域的类文件

                            String classPath = this.getClass().getResource("/").getPath();

                            targetClass.writeFile(classPath);

                            targetClass.defrost();

                            super.visitClassDef(jcClassDecl);

                        }

                    } catch (Throwable ex1) {

                        throw ex1;

                    }

                }

            });

        }

        return true;

    } catch (Throwable ex2) {

        throw ex2;

    }

}

在resources/META-INF/services路径下新建javax.annotation.processing.Processor文件,写入Processor实现类:

com.demo.annotationprocess.InsertFieldProcessor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1189466.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“位不配财”?程序员兼职,稳妥挣钱才是王道!

一、配不上 戏称程序员为“码农”&#xff0c;一年到头&#xff0c;像那地里的老黄牛和勤勤恳恳的老农民。 又像极了那工地上的农民工&#xff0c;天天搬砖&#xff0c;苦得嘞。 作为推动时代进步的得力干将&#xff0c;工作量自然是不容小觑。说程序员不加班都没人信&#x…

【前段基础入门之】=>CSS3新特性 文本多列 布局

概述&#xff1a; 作用&#xff1a;专门用于实现类似于报纸的布局。属于是一行文本多列布局 属性/值描述column-count 指定列数&#xff0c;值是数字。column-width指定列宽&#xff0c;值是长度单位columns同时指定列宽和列数&#xff0c;复合属性&#xff1b;值没有数量和顺序…

2023年眼镜行业分析(京东眼镜销量数据分析):市场规模同比增长26%,消费需求持续释放

随着我国经济的不断发展&#xff0c;电子产品不断普及&#xff0c;低龄及老龄人口的用眼场景不断增多&#xff0c;不同年龄阶段的人群有不同的视力问题&#xff0c;因此&#xff0c;视力问题人口基数也随之不断加大&#xff0c;由此佩戴眼镜的人群也不断增多。 同时&#xff0c…

Unreal Engine 学习笔记 (3)—— 导入资源

1.导入FBX文件 打开系统文件管理器按下鼠标左键拖动fbx文件到UE编辑器中松开鼠标左键在弹出对话框FBX导入选项页面中&#xff0c;选择对应的骨骼 重定向骨骼 拖动UE4的walk_strafe_back.fbx文件到UE5编辑器中 在弹出的FBX导入选项对话框中选择UE4对应的骨骼 使用重定向资产…

软文推广中如何搭建媒体矩阵

媒体矩阵简单理解就是在不同的媒体平台上&#xff0c;根据运营目标和需求&#xff0c;建立起全面系统的媒体布局&#xff0c;进行多平台同步运营。接下来媒介盒子就来和大家聊聊&#xff0c;企业在软文推广过程中为什么需要搭建媒体矩阵&#xff0c;又该如何搭建媒体矩阵。 一、…

el-table实现单选和隐藏全选框和回显数据

0 效果 1 单选 <el-table ref"clientTableRef" selection-change"clientChangeHandle"><el-table-column fixed type"selection" width"50" align"center" /><el-table-column label"客户名称" a…

Spring Boot中配置多个数据源

配置数据源实际上就是配置多个数据库&#xff0c;在一个配置文件中配置多个数据库&#xff0c;这样做主要的好处有以下几点&#xff1a; 数据库隔离&#xff1a;通过配置多个数据源&#xff0c;可以将不同的业务数据存储在不同的数据库中&#xff0c;实现数据的隔离。这样可以…

第四章:人工智能深度学习教程-激活函数(第四节-深入理解激活函数)

什么是激活函数&#xff1f; 在人工神经网络中&#xff0c;节点的激活函数定义了该节点或神经元对于给定输入或一组输入的输出。然后将该输出用作下一个节点的输入&#xff0c;依此类推&#xff0c;直到找到原始问题的所需解决方案。 它将结果值映射到所需的范围&#xff0c;例…

渲染管线详解

光栅化的渲染管线一般分为三大阶段&#xff1a;应用程序阶段->几何阶段->光栅化阶段 也可以四大阶段&#xff1a; 应用程序阶段->几何阶段->光栅化阶段->逐片元操作阶段 更详细的流程如下&#xff1a; Vertex Specification&#xff08;顶点规范化&#xff09…

GPTZero:论文打假神器

记住这张脸他是全美学生的公敌。 别的学生在AI大浪潮间翻云覆雨&#xff0c;有的用GPT代写作业&#xff0c;有的用GPT代工论文&#xff0c;大家都忙的不亦乐乎。 正在大家都在欢呼雀跃跟作业拜拜时&#xff0c;就是这个小伙&#xff0c;普林斯顿大学的华裔小天才Edward Tian…

Git入门---简介,常用命令

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.Git 的简介 1.1. 介绍 Git是一个开源的分布式版本控制系统&#xff0c;最初由Linus Torvalds于2005年创…

Adobe Illustrator 2021 下载及安装教程

目录 下载地址&#xff1a; 安装教程&#xff1a; 下载地址&#xff1a; Adobe Illustrator 2021安装包 链接&#xff1a;https://pan.baidu.com/s/1UIzjbS5pRuL7Zpt9RrU5lQ 提取码&#xff1a;lxwj 安装教程&#xff1a; 1、下载压缩包,解压文件 2、双击Set_up.exe&#…

数据分析实战 | 线性回归——女性身高与体重数据分析

目录 一、数据集及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 八、模型评价 九、模型调参 十、模型预测 实现回归分析类算法的Python第三方工具包比较常用的有statsmodels、statistics、scikit-learn等&#…

刚接触银行新业务测试的一些问题

在银行金融领域的测试工作&#xff0c;相信很多测试工程师都会遇到自己不熟悉的业务。然后开始看文档&#xff0c;问开发或者需求人员。搞懂了大概的流程&#xff0c;然后开始进行测试。 不过遇到复杂的业务情况时&#xff0c;真的很需要时间去梳理。而且测试环境的配置问题、不…

ruoyi前后端分离版本开发框架解读---让你快速入门

后端结构 com.ruoyi ├── common // 工具类 │ └── annotation // 自定义注解 │ └── config // 全局配置 │ └── constant // 通用常量 │ └── core …

【Linux】第十四站:进程优先级

文章目录 一、Linux内核怎么设计各种结构二、进程优先级1.基本概念2.是什么3.为什么要有优先级4.批量化注释操作5.查看优先级6.PRI and NI 三、位图与优先级 一、Linux内核怎么设计各种结构 我们前面所写的数据结构都是比较单纯的。 而linux中就比较复杂了&#xff0c;同一个…

深入分析MySQL索引与磁盘读取原理

索引 索引是对数据库表中一列或者多列数据检索时&#xff0c;为了加速查询而创建的一种结构。可以在建表的时候创建&#xff0c;也可以在后期添加。 USER表中有100万条数据&#xff0c;现在要执行一个查询"SELECT * FROM USER where ID999999"&#xff0c;如果没有索…

数据结构与算法C语言版学习笔记(5)-串,匹配算法、KMP算法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、串的定义二、串的存储结构1.顺序结构2.链式结构 三、串的朴素的模式匹配算法&#xff08;暴力匹配算法&#xff09;1.背景2.假设我们要从下面的主串 S"…

VS Code+DevChat助力非专业开发也能玩转代码编程

一、前言 偶然间网上瞎逛&#xff0c;看到DevChat 发布了一款 VS Code 插件&#xff0c;可提供类似chatgpt一样的“一站式 AI 辅助编程”体验。据说&#xff0c; DevChat 直接对接 GPT-4 还让免费用&#xff0c;目前免费注册收邮件即可获取key&#xff0c;再也不用麻烦的外部手…