可代替 ASM,使用 AnnotationProcessor 做程式碼插樁
說到程式碼插樁,你可能會想到AspectJ
、Transfrom Api + ASM
等等。
程式碼插樁的用處自不必說,可以做埋點、熱修復、元件化路由等等。
然而,AspectJ
感覺不好用,ASM
比較複雜,需要自定義 gradle 外掛。好在前段時間,我遇到了新的方法 ——AnnotationProcessor
。(下面簡稱為apt
)
apt
是否只能生成新的 java 檔案?還是有什麼方法可以直接插入程式碼,達到 ASM 的效果?
留個懸念,咱們接著往下看。
2. apt 與 ButterKnife
說到 apt,不得不說 ButterKnife。
通過註解生成XXX_ViewBinding
的操作深入人心,然後Javapoet
也逐漸家喻戶曉。
回顧一下,以下是jdk
中提供的apt
相關的 api。
- javax - annotation.processing - AbstractProcessor// 入口 - ProcessingEnvironment// 編譯器環境,可理解為 Application - Filer// 檔案讀寫 util - lang.model - element - Element// 程式碼結構資訊 - type - TypeMirror// 編譯時的型別資訊(非常類似 Class,但那是執行時的東西,注意現在是編譯時)
一個常規的註解處理器有這麼幾步:
AbstractProcessor Element Filer app/build/generated/source/apt/
然而,Filer
有侷限性,只有 create 相關的介面。
public interface Filer { JavaFileObject createSourceFile(CharSequence name, Element... originatingElements) throws IOException; ... }
我們得尋找別的方式。
3. javac 與 重寫 AST
讓我們來思考一個問題:
- AbstractProcessor.process() 這個入口是被什麼東西所呼叫的呢?
當然是編譯器啦,通常而言,我們一般用的是javac
編譯器。
現在,我們只需要通讀一下 javac 的
),就會發現,編譯流程大致如下:
-
Parse and Enter
:
解析 .java 檔案
,在記憶體中生成AST (抽象語法樹) 、填充符號表
-
Annotation Processing
: 呼叫
AbstractProcessor.process()
,若有新的 java 檔案生成,則回到步驟 1 -
Analyse and Generate
: 依次執行
標註檢查
、資料及控制分析
、解語法糖
、生成並寫入.class檔案
如此一來,我們知道了我們編寫的apt
程式碼執行在 java 編譯過程中的第2步。
如果說,編譯過程是.java -> AST -> .class
的過程,那麼我們可以在apt
裡修改AST
這個中間產物,改變最終的.class
,從而達到等同於ASM
的效果。
具體而言,我們需要用到一些javac
內部的 api,它們不屬於 jdk 的java/
或者javax/
包下。而是在tools.jar
的com.sun.tools.javac/
下,具體不再展開。
AST 詳細介紹:安卓AOP之AST:抽象語法樹
4. 一個例子,一行註解搞定單例
設想,我現在有一個UserManager
,想搞成單例。
按照原本的生成新檔案的方式肯定是不行的。不過現在我們可以插入程式碼。
-
自定義一個註解
@Singleton
,以及一個註解處理器SingletonProcessor
-
原始碼加一行
@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() { } } }
實現細節請移步:https://github.com/fashare2015/java-sugar
5. 後記
作為 java 的忠實粉絲,希望搞幾個語法糖出來。因此,胡亂搗鼓出了java-sugar
這個專案。
其中實現了單例
、Builder
、觀察者
等幾個常用的設計模式。
另外還做了自動生成Getter
和Setter
,這樣一來,java
應該不輸給kotlin
了吧(滑稽)。
也許,大致上可以把kotlin
的語法糖都抄襲一遍?