java注解处理器——在编译期修改语法树

目录

 

前言

从需求说起

添加打印日志代码的方案

第一种方案,硬编码

第二种方案,AOP

第三种方案,修改class文件

注解处理器的用法:

1、实现AbstractProcessor

2、添加注解

名称分析模块的思想及设计

语法树的操作:

JCTree的介绍

​​

TreeMaker

TreeMaker.Modifiers

TreeMaker.ClassDef

TreeMaker.MethodDef

TreeMaker.VarDef

TreeMaker.Ident

TreeMaker.Return

TreeMaker.NewClass

TreeMaker.Select

TreeMaker.Apply

TreeMaker.Exec

TreeMaker.Block

用法实例:

1、根据字符串获取Name,(利用Names的fromString静态方法)

2、创建变量语句

3、创建 域/方法 的多级访问, 方法的标识只能是最后一个

4、声明变量并赋值(利用以上包装的方法)

5、给变量赋值

6、两个字符串字面量相加并赋值

7、+=语句

8、声明整型变量并赋值

9、++语句

10、加法语句

11、方法调用(以输出语句举例)

12、if语句

13、if语句(null判断)


前言

从需求说起

由于相关政策,最近公司安全部要求各系统在rpc接口调用的交互过程中把相应的参数及结果以相应的格式发送到安全部统一记录,例如参数或结果含手机号和邮箱则格式如:“mail:axxx@126.com,phone:183xxxx1967”,其它系统信息等先忽略。

以便在数据泄露时可据此分析出数据的泄露源头,以及若有黑客攻克有些接口时公司能有迹可循。

总体架构是各个接口把入参和结果打印日志,然后由统一的日志收集器收集日志通过mq发送到安全部。这样每个系统只用在接口中添加参数和结果的打日志代码。

添加打印日志代码的方案

第一种方案,硬编码

即直接在接口中编写打印日志的代码。这种工作量太大,公司各个部门,以往积累了众多的项目,这样改造的工作量太大。

第二种方案,AOP

利用aop框架,在切面类中打印日志。可以使用spring 支持的aop功能或其他aop框架。

这个方案应该来说改动及工作量都大大降低,公司也是采用的这种方案。但是其弊端也很明显,

一、是对框架的依赖(如用spring aop的话则非spring项目则不适用)

二、就是不同的项目或接口,入参或结果变量名不同,如手机号:有的叫mobilePhone, 有的叫telephone等;但打印日志时要统一打印,如:phone:183xxxx1967; 所以要在参数上加注解,以表明打印日志时的名称。这个重复工作量也不小。

第三种方案,修改class文件

针对第二种方案的弊端,我设计出这第三种方案。

利用相关技术,直接修改class文件,在接口中添加打印日志的字节码。例如Javassist,asm等技术。

通过调研,在编译期通过修改语法树来达到修改class文件的效果,这种对用户来说完全透明,不依赖任何框架。针对弊端二则发明名称分析模块,让程序自动分析出参数的含义,从而避免了手工添加注解的麻烦。

下面就具体说明第三种方案的实现:

利用JDK的注解处理器,可在编译期间对注解处理,可以读取、修改、添加抽象语法树中的任意元素。

注解处理器是JDK1.6开始提供的功能,利用注解处理器可以干涉编译器的行为,只要有足够的创意,可以利用注解处理器实现许多原本只能在编码中完成的事情。

注解处理器的用法:

1、实现AbstractProcessor

实现init和process方法

顾名思义,init是完成一些初始化工作;process完成具体的逻辑处理。后边会有具体的例子说明。

2、添加注解

@SupportedAnnotationTypes 指定此注解处理器支持的注解,可用*指定所有注解

@SupportedSourceVersion 指定支持的java的版本

注解实例:

 

 

 

在process方法中可获取到注解有@Safety的类和方法。

Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Safety.class);

然后可遍历方法,获取到方法的入参,分析入参,给方法的语法树添加打印参数日志的代码。对于方法的结果是同样的道理。

关于对语法树的操作,网上资料相对较少及较为片段,介绍起来篇幅略长,故放在最后面进行介绍。

名称分析模块的思想及设计

剩下的一个关键问题是如何把不同的参数名统一成打印日志时的名称,例如参数名为mobilePhone或telephone,但要打印的是phone。如果在注解属性中指定的话,通过注解可以获取到,但是当接口或参数很多的情况下也是一件重复性的力气活。

故我设计出一种不让开发人员手动指定名称的方案,既对老的项目修改的少,又减轻开发人员的工作量,对新项目的应用也是高效率的。

如图:

词库存储(可用类的静态字段存储)需要打印的日志名称及其对应的词根及单词。如:

上图红框为打印日志时要打印的名称。绿框中为词根及单词:如若业务参数有用postbox作为邮箱变量名的则也可把postbox加入到mail的词库中。

这样当业务参数为mobilePhone或telephone时,名称分析模块能够分析出参数名包含phone词根,从而得到对应的打印日志名“phone”;这就要求业务参数的名义要有具体的含义,不能随便字母组合没有含义的词语,这应该也是每个公司开发时的基本要求。

这里只是举例一个简单的可行性方案,名称分析模块也可利用AI技术,根据输入的变量名利用智能技术分析出此变量名的含义。

语法树的操作:

下面对语法树的操作进行详细的说明,这里需要提到三个类:

  1. JavacTrees 提供了待处理的抽象语法树
  2. TreeMaker 封装了创建AST节点的一些方法
  3. Names 提供了创建标识符的方法

可在init方法中对这三个类初始化,以便在process方法中利用它们对语法树进行操作。如图:

 

AST(抽象语法树)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构,例如包、类型、修饰符、运算符、接口、返回值,甚至代码注释等都可以是一个语法结构。

JCTree的介绍

JCTree

JCTree是语法树元素的基类。

如上图,它包含两个属性,

字段type表示语法结构的类型

字段pos用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,即使创建了也没有意义,而要用TreeMaker来进行操作。

重点介绍几个JCTree的子类:

  1. JCStatement:声明语法树节点,常见的子类如下
    • JCBlock:语句块
    • JCReturn:return语句
    • JCClassDecl:类定义
    • JCVariableDecl:字段/变量定义
    • JCIf: if语句

   2.JCMethodDecl:方法定义语法树节点

   3.JCModifiers:访问标志语法树节点

   4.JCExpression:表达式语法树节点,常见的子类如下

    • JCAssign:赋值语句
    • JCAssignOp:+=
    • JCIdent:标识符,可以是变量,类型,关键字等等
    • JCLiteral: 字面量表达式,如123, “string”等
    • JCBinary:二元操作符

JCTree的子类很多,大部分可以从字面上看出其意义

### Lombok 插件功能与原理 Lombok 插件的核心在于其利用了Java编译期的注解处理机制,即JSR269标准。该标准允许开发者创建自定义注解处理器,在编译阶段介入并修改抽象语法树(AST),从而动态地向类中注入额外的方法或字段[^4]。 #### 编译期间的工作流程 当带有特定Lombok注解的Java源文件被送入Javac或其他兼容编译器时: - **初始化**:启动过程中加载所有注册过的注解处理器。 - **扫描与匹配**:遍历输入的源代码寻找已知类型的注解标记。 - **转换操作**:一旦发现目标注解,则触发相应事件驱动程序执行预设动作——例如为`@Getter/@Setter`增加属性访问接口;对于`@ToString`则构建描述对象状态字符串表达式的逻辑等[^1]。 #### 抽象语法树的操作 具体来说,Lombok会解析待编译单元内的每一个声明节点,并依据所附加元数据指示采取行动。比如遇到`@Builder`标注下的构造函数请求时,便会按照既定模板合成一系列辅助成员变量及其对应的组装方法链路结构。 ```java // 用户视角下简洁明了的原始定义形式 public class Person { private final String name; @Builder public Person(String name) { this.name = Objects.requireNonNull(name); } } // 经过Lombok增强后的实际效果相当于展开成如下详尽版本 public static class __Person_Builder { // ...省略其他内部细节... } ``` 这种设计模式不仅提高了开发效率还保持了一定程度上的灵活性,使得最终产物既能满足业务需求又不失优雅性。 #### IDE集成支持 值得注意的是为了使IDE具备同样的智能感知能力,还需要单独安装配套扩展组件以便同步更新编辑环境内有关新生成元素的信息索引。这样可以确保实时反馈诸如自动补全建议等功能特性[^3]。
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值