可代替 ASM,使用 AnnotationProcessor 作代码插桩

1. 前言

说到代码插桩,你可能会想到 AspectJTransfrom Api + ASM 等等。html

代码插桩的用处自没必要说,能够作埋点、热修复、组件化路由等等。前端

然而,AspectJ感受很差用,ASM 比较复杂,须要自定义 gradle 插件。好在前段时间,我遇到了新的方法 —— AnnotationProcessor。(下面简称为 aptjava

apt 是否只能生成新的 java 文件?仍是有什么方法能够直接插入代码,达到 ASM 的效果?git

留个悬念,我们接着往下看。github

2. apt 与 ButterKnife

说到 apt,不得不说 ButterKnife。设计模式

经过注解生成XXX_ViewBinding的操做深刻人心,而后Javapoet也逐渐家喻户晓。api

回顾一下,如下是 jdk 中提供的 apt 相关的 api。bash

- javax
  - annotation.processing
    - AbstractProcessor       // 入口
    - ProcessingEnvironment   // 编译器环境,可理解为 Application
    - Filer                   // 文件读写 util
  - lang.model
    - element
      - Element               // 代码结构信息
    - type
      - TypeMirror            // 编译时的类型信息(很是相似 Class,但那是运行时的东西,注意如今是编译时) 
复制代码

一个常规的注解处理器有这么几步:app

  1. 继承 AbstractProcessor
  2. 根据注解获取相关 Element
  3. 写入 Filer
  4. app/build/generated/source/apt/下将生成相关 java 文件

然而,Filer 有局限性,只有 create 相关的接口。组件化

public interface Filer {
    JavaFileObject createSourceFile(CharSequence name, Element... originatingElements) throws IOException;
    ...
}
复制代码

咱们得寻找别的方式。

3. javac 与 重写 AST

让咱们来思考一个问题:

  1. AbstractProcessor.process() 这个入口是被什么东西所调用的呢?

固然是编译器啦,一般而言,咱们通常用的是javac编译器。

如今,咱们只须要通读一下 javac 的源码java 编译过程概览),就会发现,编译流程大体以下:

  1. Parse and Enter: 解析 .java 文件,在内存中生成 AST (抽象语法树)填充符号表
  2. Annotation Processing: 调用 AbstractProcessor.process(),如有新的 java 文件生成,则回到步骤 1
  3. Analyse and Generate: 依次执行标注检查数据及控制分析解语法糖生成并写入.class文件

如此一来,咱们知道了咱们编写的apt代码执行在 java 编译过程当中的第2步。

若是说,编译过程是 .java -> AST -> .class 的过程,那么咱们能够在apt里修改AST这个中间产物,改变最终的.class,从而达到等同于ASM的效果。

具体而言,咱们须要用到一些 javac 内部的 api,它们不属于 jdk 的java/或者javax/包下。而是在 tools.jarcom.sun.tools.javac/ 下,具体再也不展开。

AST 详细介绍:安卓AOP之AST:抽象语法树

4. 一个例子,一行注解搞定单例

设想,我如今有一个UserManager,想搞成单例。

按照本来的生成新文件的方式确定是不行的。不过如今咱们能够插入代码。

  1. 自定义一个注解@Singleton,以及一个注解处理器SingletonProcessor
  2. 源代码加一行@Singleton:
// UserManager.java
@Singleton
class UserManager {
}
复制代码

apt 插桩后的代码,自动生成getInstance(),以及InstanceHolder,有没有很爽:

// build 目录下,UserManager.class
@Singleton
class UserManager {

    public static UserManager getInstance() {
        return UserManager._InstanceHolder._sInstance;
    }

    UserManager() {
    }

    private static class _InstanceHolder {
        private static final UserManager _sInstance = new UserManager();

        private _InstanceHolder() {
        }
    }
}
复制代码

实现细节请移步:github.com/fashare2015…

5. 后记

做为 java 的忠实粉丝,但愿搞几个语法糖出来。所以,胡乱捣鼓出了java-sugar这个项目。

其中实现了单例Builder观察者等几个经常使用的设计模式。

另外还作了自动生成GetterSetter,这样一来,java应该不输给kotlin了吧(滑稽)。

也许,大体上能够把 kotlin 的语法糖都抄袭一遍?

6. 参考

openjdk.java.net/groups/comp…

Java编译(二)Java前端编译: Java源代码编译成Class文件的过程

Javac黑客指南

安卓AOP之AST:抽象语法树

Lombok

相关文章
相关标签/搜索