APT原理与ASM

2,195 阅读4分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战

一.策略模式

在运行时能够采用不同的选择策略来完成任务。策略定义为接口,不同的选择为策略的不同实现。它用在隔离的服务中,运行时环境不关心具体的服务实现,将服务实现提供给外部。

有三种角色 Context 运行时环境,Strategy 为策略接口,StrategyA,StrategyB,StrategyC 为策略实现。

image

二.APT原理

apt采用策略模式来实现注解的处理。Processor为接口,我们提供的xxxProcessor为实现。

2.1 LogProcessor.process方法如何被调用

假设我们要定义的注解为LogProcessor , 处理具体步骤为:

  • 1.在main\resources\META-INF\services中定义javax.annotation.processing.Processor这个配置文件,其中的内容就是刚刚创建的LogProcessor全限定名称如com.xxx.LogProcessor
  • 2.gradle在编译过程中调用javac,逐步深入直到ServiceLoader.load(xxx)
  • 3.ServiceLoader根据固定的路径加载上述配置文件,通过反射获取我们注解实例。
  • 4.调用logProcessor.process(env,set)方法开始处理注解逻辑。

2.2 LogProcessor.process被调用几次

根据注解生成新的文件的个数来确定,每个新生成的文件都会导致process方法被多调用一次,所有文件处理完后最后会回调一个set为空的调用。因此总调用次数为: 新生成的文件个数 + 包含对应注解的原文件执行次数(1) + 1

2.3 LogProcessor.process返回值boolean的含义

由于注解处理器是链式调用,返回true表示注解是否继续向后传递注解集合set,true表示不再传递,false表示继续传递。

三.ASM

3.1 逆波兰表达式

java方法在虚拟机中是以栈帧的形式执行的,栈帧处理的流程就是逆波兰表达式中操作数的出栈流程。

逆波兰表达式将中缀表达式转化为后缀表达式:

5 + ((1 + 2) * 4) - 3--> 5 1 2 + 4 * + 3 -

3.2 ASM 执行流程

ASM操作的字节码的过程就是修改字节码执行流程的过程,修改内容按照逆波兰表达式的顺序插入修改内容。

在方法的开始和结束插入日志的整体流程包括:

graph LR
ClassReader(class)-->ClassVisitor(class)
ClassVisitor(class)-->MethodVisitor(method)
MethodVisitor(method)-->methodEnter
MethodVisitor(method)-->methodExit
methodEnter-->insertEnterLog
methodExit-->insertExitLog
insertEnterLog-->ClassWriter
insertExitLog-->ClassWriter
ClassWriter-->byteArray
byteArray-->文件

3.3 如何便捷的写入字节码

  • 1.将要插入的最终代码写在某个Java文件中
  • 2.通过ASM ByteViewer 查看字节码信息
  • 3.通过ASMified直接获取要插入的字节码对应的java代码
  • 4.插入到自定义的visotor中。

image

四.合作

APT与ASM是合作关系

  • apt通过策略模式,将要处理的注解开发给开发者,也用于过滤需要处理的class文件,一般选择将完整类名+注解类型记录到指定的文件夹。开发者通过集成AbstractAnnotationProcessor并实现process方法来处理注解。Element模型用于对应源文件中的中的class模型
  • 有了要处理的class文件后,在transform之前,gradle开放了transform流程给开发者来处理class文件。我们需要自定义plugin,并注册自己的transform到系统中。这样由apt处理过的class就会通过transform进行实际的编辑或者生成。
  • 在transfrom阶段编辑class字节码常见的有javassist和asm两种,javassist通过反射以类java的形式提供链式编辑api非常方便使用,但是性能一般。asm直接操作字节码,通过访问者模式遍历class并修改,一般会结合ByteViewer这个插件来拷贝需要插入的class字节码,不方便使用,但是性能很高。

五.总结

  • 1.apt采用策略模式将注解处理器的能力外放给程序员处理
  • 2.apt通过ServiceLoader反射获取注解处理器,在javac的执行过程中触发process方法执行注解处理。处理次数为新生成文件个数+2,返回值决定了处理的注解是否向后传递。
  • 3.字节码在虚拟机中以逆波兰表达式存储,采用操作数栈来辅助执行。
  • 4.asm插入的字节码需要满足逆波兰表达式的执行规则
  • 5.可以用ASM ByteViewer 来简化字节码的开发过程。