總結 Android、Java 10種實現 AOP 方式

面向切面編程(Aspect-Oriented Programming)搂根,是Java開發(fā)中常見的編程方式,很多著名的開源框架都是基于AOP思想實現铃辖,比如Spring剩愧、Retrofit、Mybatis等娇斩,實現AOP的方式有很多種仁卷,主要分為三類:靜態(tài)編譯穴翩、編譯期注入代碼動態(tài)編譯锦积,前兩者都是在生成jar芒帕、dex包之前就完成代碼處理,動態(tài)編譯是在運行時實現丰介,比如補丁就是動態(tài)編譯實現背蟆。

示例圖

.java
<靜態(tài)編譯>
.class
<編譯期注入代碼>
.dex
運行時
<動態(tài)編譯與動態(tài)生成字節(jié)碼、動態(tài)代理>

分類

我在這里總結了10種AOP的方式

名稱 說明 支持
APT 靜態(tài)編譯 Java基矮、Android
AspectJ 使用專門的編譯器淆储,在編譯期插入代碼 Java、Android
Javassist 動態(tài)編譯與動態(tài)生成字節(jié)碼 Java家浇、Android
CGLIB 動態(tài)編譯與動態(tài)生成字節(jié)碼 Java
ByteBuddy 動態(tài)編譯與動態(tài)生成字節(jié)碼 Java、Android
JDK動態(tài)代理 動態(tài)代理 Java碴裙、Android
ASM 動態(tài)編譯與動態(tài)生成字節(jié)碼 Java钢悲、Android
ASMDEX 動態(tài)編譯與動態(tài)生成字節(jié)碼 Android
DexMaker 動態(tài)編譯與動態(tài)生成字節(jié)碼 Android
Xposed 需要root權限,在運行時插入字節(jié)碼 Android

APT(Annotation Processing Tool)

APT技術Java應用并不是特別廣泛舔株,但是在Android中是主要實現AOP的方式之一莺琳,通過注解實現在編譯期間生成代碼,執(zhí)行效率高载慈。

代表框架:ButterKnife惭等、GRouterAptPreferences

教程

Android APT(編譯時代碼生成)最佳實踐

AspectJ

使用AspectJ編譯器(ajc)办铡,在編譯時期辞做,在關鍵的的地方插入部分代碼,處理相關邏輯寡具,比如可以用于打印方法執(zhí)行的效率秤茅,權限檢查等,Spring就使用了AspectJ童叠。在Android上的應用主要是做性能監(jiān)控框喳、基于注解的數據埋點等。

代表框架:Hugo厦坛、Spring

教程

AspectJ AOP教程:實現Android基于注解無侵入埋點五垮、性能監(jiān)控

CGLIB

CGLIB 是一個強大的,高性能杜秸,高質量的Code生成類庫 放仗,是基于ASM封裝,大名鼎鼎的Hibernate就是基于CGLIB實現亩歹,CGLIB是一個應用非常廣泛的AOP框架匙监。

代表框架:Hibernate

+--- cglib:cglib:3.3.0
|    \--- org.ow2.asm:asm:7.1
教程

添加依賴

dependencies {
    implementation 'cglib:cglib:3.3.0'
}

代碼

public class CglibTest implements MethodInterceptor {
    public static void main(String[] args) {
        User user = (User) new CglibTest().getProxy(User.class);
        user.setId(1);
        user.getId();
    }
    public Enhancer enhancer = new Enhancer();
    private Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("執(zhí)行開始:" + method);
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("執(zhí)行結束:" + method);
        return result;
    }
    public static class User {
        private int id;
        public int getId() {return id;}
        public void setId(int id) {this.id = id;}
    }
}

結果

執(zhí)行開始:public void com.thejoyrun.aptpreferences.CglibTest$User.setId(int)
執(zhí)行結束:public void com.thejoyrun.aptpreferences.CglibTest$User.setId(int)
執(zhí)行開始:public int com.thejoyrun.aptpreferences.CglibTest$User.getId()
執(zhí)行結束:public int com.thejoyrun.aptpreferences.CglibTest$User.getId()

ByteBuddy

Byte Buddy 是一個運行時動態(tài)生成字節(jié)碼的庫凡橱,它是依賴Java虛擬機,由于Android的字節(jié)碼和Java不同亭姥,也提供了Android的支持包稼钩,大名鼎鼎的測試框架 Mockito 就是基于ByteBuddy實現,Mockito3也正式支持在Android運行時環(huán)境使用达罗,但是包大小為3.3MB坝撑,建議只在debug環(huán)境下使用。

api 'net.bytebuddy:byte-buddy:1.10.1'
api 'net.bytebuddy:byte-buddy-android:1.10.1'

代表框架:AopPreferences粮揉、Mockito

教程

添加依賴

dependencies {
    implementation 'net.bytebuddy:byte-buddy:1.10.1'
    // implementation 'net.bytebuddy:byte-buddy-android:1.10.1'
}

代碼

public class ByteBuddyTest {
    public static void main(String[] args) {
        User user = new ByteBuddyTest().getProxy(User.class);
        user.setId(1);
        user.getId();
    }
    private <T> T getProxy(Class<T> clazz) {
        Class<? extends T> loaded = new ByteBuddy().subclass(clazz)
                .method(ElementMatchers.<MethodDescription>any())
                .intercept(MethodDelegation.to(ByteBuddyTest.class))
                .make().load(getClass().getClassLoader()).getLoaded();
        // 如果是 Android
        // .load(getClass().getClassLoader(), new AndroidClassLoadingStrategy.Wrapping(StorageAndroid.context.getDir("dexgen", Context.MODE_PRIVATE))).getLoaded();
        try {
            return loaded.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    @RuntimeType
    public static Object intercept(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable) throws Exception {
        System.out.println("執(zhí)行開始:" + method);
        Object result = callable.call();
        System.out.println("執(zhí)行結束:" + method);
        return result;
    }
    public static class User {
        private int id;
        public int getId() {return id;}
        public void setId(int id) {this.id = id;}
    }
}

結果

執(zhí)行開始:public void com.thejoyrun.aptpreferences.ByteBuddyTest$User.setId(int)
執(zhí)行結束:public void com.thejoyrun.aptpreferences.ByteBuddyTest$User.setId(int)
執(zhí)行開始:public int com.thejoyrun.aptpreferences.ByteBuddyTest$User.getId()
執(zhí)行結束:public int com.thejoyrun.aptpreferences.ByteBuddyTest$User.getId()

JDK動態(tài)代理

JDK動態(tài)代理是基于JDK自帶實現AOP的方式巡李,缺點就是只支持接口代理。

代表框架:Retrofit扶认、Mybatis

教程

由于是基于JDK開發(fā)侨拦,所以不需要引入第三方包

public class InvocationHandlerTest implements InvocationHandler {
    public static void main(String[] args) {
        User user = new InvocationHandlerTest().getProxy(User.class);
        user.setId(1);
        System.out.println("Id: " + user.getId());
    }
    private <T> T getProxy(Class<T> clazz) {
        T result = (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, new InvocationHandlerTest());
        return result;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("執(zhí)行開始:" + method);
        if (method.getName().startsWith("set")) {
            System.out.println("參數:" + args[0]);
        } else {
            Object result = new Random().nextInt();
            System.out.println("執(zhí)行結果:" + result);
            return result;
        }
        return null;
    }
    public interface User {
        int getId();
        void setId(int id);
    }
}

結果

執(zhí)行開始:public abstract void com.thejoyrun.aptpreferences.InvocationHandlerTest$User.setId(int)
參數:1
執(zhí)行開始:public abstract int com.thejoyrun.aptpreferences.InvocationHandlerTest$User.getId()
執(zhí)行結果:165584246
Id: 165584246

ASM

ASM 是一個 Java 字節(jié)碼操控框架,直接操作字節(jié)碼指令辐宾,執(zhí)行效率高狱从,要是使用者掌握Java類字節(jié)碼文件格式及指令,對使用者的要求比較高叠纹。所以不建議直接使用季研,建議使用更加高階的Javassist框架。

ASMDEX

ASMDEX 是SAM開發(fā)誉察,針對Android字節(jié)碼的框架与涡。

Javassist

Javassist是基于ASM,提供了更高級的API持偏,執(zhí)行效率比ASM差一些驼卖,但無需掌握字節(jié)碼指令的知識,對使用者要求較低综液,多數的Android熱修復框架都是基于Javassist實現款慨。

代表框架:InstantRun、HotFix谬莹。

教程

Javassist 實現AOP檩奠、動態(tài)創(chuàng)建代碼

基于 javapoet 實現 Java 代碼文件生成

DexMaker

DexMaker 用于執(zhí)行針對Dalvik VM的編譯時或運行時代碼生成的Java語言API,類似CGLIB和ASM附帽。

Xposed

Xposed框架是一套開源的埠戳、在Android高權限模式下運行的框架服務,可以在不修改APK文件的情況下影響程序運行(修改系統)的框架服務蕉扮,基于它可以制作出許多功能強大的模塊整胃,且在功能不沖突的情況下同時運作,通常是用于實現特殊的編程喳钟,比如逆向工程屁使,自動化測試在岂。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蛮寂,隨后出現的幾起案子蔽午,更是在濱河造成了極大的恐慌,老刑警劉巖酬蹋,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件及老,死亡現場離奇詭異,居然都是意外死亡范抓,警方通過查閱死者的電腦和手機骄恶,發(fā)現死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匕垫,“玉大人僧鲁,你說我怎么就攤上這事∧甓校” “怎么了悔捶?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長单芜。 經常有香客問我,道長犁柜,這世上最難降的妖魔是什么洲鸠? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮馋缅,結果婚禮上扒腕,老公的妹妹穿的比我還像新娘。我一直安慰自己萤悴,他們只是感情好瘾腰,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著覆履,像睡著了一般蹋盆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上硝全,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天栖雾,我揣著相機與錄音,去河邊找鬼伟众。 笑死析藕,一個胖子當著我的面吹牛,可吹牛的內容都是我干的凳厢。 我是一名探鬼主播账胧,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼竞慢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了治泥?” 一聲冷哼從身側響起筹煮,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎车摄,沒想到半個月后寺谤,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡吮播,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年变屁,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片意狠。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡粟关,死狀恐怖,靈堂內的尸體忽然破棺而出环戈,到底是詐尸還是另有隱情闷板,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布院塞,位于F島的核電站遮晚,受9級特大地震影響,放射性物質發(fā)生泄漏拦止。R本人自食惡果不足惜县遣,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汹族。 院中可真熱鬧萧求,春花似錦、人聲如沸顶瞒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榴徐。三九已至守问,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箕速,已是汗流浹背酪碘。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盐茎,地道東北人兴垦。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親探越。 傳聞我的和親對象是個殘疾皇子狡赐,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內容

  • 前前言 相信大家在入門 AOP 時,常常被繁多的術語钦幔、方法和框架繞暈枕屉。AOP 好像有點耳熟?Javaseopt 是...
    FeelsChaotic閱讀 36,246評論 6 91
  • 1. AOP與OOP的區(qū)別 平時我接觸多的就是OOP(Object Oriented Programming面向對...
    生椰拿鐵錘閱讀 2,388評論 3 22
  • 一鲤氢、概述 ??代理模式我們接觸的就比較多了搀擂,所謂的代理模式就是,給某一個對象提供一個代理對象卷玉,并由代理對象控制對原...
    騎著烏龜去看海閱讀 907評論 0 9
  • Aop編程是一種區(qū)別OOP編程的概念哨颂,從切面的角度看待問題,這篇文章主要講述了Java開發(fā)中常用的Aop開發(fā)方式以...
    西華子閱讀 193評論 0 1
  • Aop編程是一種區(qū)別OOP編程的概念相种,從切面的角度看待問題威恼,這篇文章主要講述了Java開發(fā)中常用的Aop開發(fā)方式以...
    六_六閱讀 689評論 0 1