「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」
一.策略模式
在运行时能够采用不同的选择策略来完成任务。策略定义为接口,不同的选择为策略的不同实现。它用在隔离的服务中,运行时环境不关心具体的服务实现,将服务实现提供给外部。
有三种角色 Context 运行时环境,Strategy 为策略接口,StrategyA,StrategyB,StrategyC 为策略实现。
二.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中。
四.合作
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 来简化字节码的开发过程。