目录
1、根据字符串获取Name,(利用Names的fromString静态方法)
前言
从需求说起
由于相关政策,最近公司安全部要求各系统在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技术,根据输入的变量名利用智能技术分析出此变量名的含义。
语法树的操作:
下面对语法树的操作进行详细的说明,这里需要提到三个类:
- JavacTrees 提供了待处理的抽象语法树
- TreeMaker 封装了创建AST节点的一些方法
- Names 提供了创建标识符的方法
可在init方法中对这三个类初始化,以便在process方法中利用它们对语法树进行操作。如图:
AST(抽象语法树)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构,例如包、类型、修饰符、运算符、接口、返回值,甚至代码注释等都可以是一个语法结构。
JCTree的介绍
JCTree是语法树元素的基类。
如上图,它包含两个属性,
字段type表示语法结构的类型
字段pos用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,即使创建了也没有意义,而要用TreeMaker来进行操作。
重点介绍几个JCTree的子类:
- JCStatement:声明语法树节点,常见的子类如下
-
- JCBlock:语句块
- JCReturn:return语句
- JCClassDecl:类定义
- JCVariableDecl:字段/变量定义
- JCIf: if语句
2.JCMethodDecl:方法定义语法树节点
3.JCModifiers:访问标志语法树节点
4.JCExpression:表达式语法树节点,常见的子类如下
-
- JCAssign:赋值语句
- JCAssignOp:+=
- JCIdent:标识符,可以是变量,类型,关键字等等
- JCLiteral: 字面量表达式,如123, “string”等
- JCBinary:二元操作符
JCTree的子类很多,大部分可以从字面上看出其意义