可代替 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

相關文章
相關標籤/搜索