AOP之AspectJ

aop實(shí)現(xiàn)的三大方式(反射 (xutil) apt注解(ButterKnife) aspect (本文即將講到的)) 說出各自的優(yōu)缺點(diǎn)

一汹粤、AOP概念

百度百科中對(duì)AOP的解釋如下:
在軟件業(yè)田晚,AOP為Aspect Oriented Programming的縮寫贤徒,意為:面向切面編程汇四,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)踢涌。

AOP是OOP的延續(xù)睁壁,是軟件開發(fā)中的一個(gè)熱點(diǎn),也是很多框架如 java中的Spring框架中的一個(gè)重要內(nèi)容行剂,是函數(shù)式編程的一種衍生范型钳降。 利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低固阁,提高程序的可重用性备燃,同時(shí)提高了開發(fā)的效率凌唬。

AOP只是一種思想的統(tǒng)稱,實(shí)現(xiàn)這種思想的方法有挺多况褪。AOP通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)测垛。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離秧均,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性锯七,提高開發(fā)效率誉己。

(1)AOP與OOP的關(guān)系

OOP(面向?qū)ο缶幊蹋?/strong>針對(duì)業(yè)務(wù)處理過程的實(shí)體及其屬性和行為進(jìn)行抽象封裝,以獲得更加清晰高效的邏輯單元?jiǎng)澐衷牖5且灿兴娜秉c(diǎn)袱蜡,最明顯的就是關(guān)注點(diǎn)聚焦時(shí),面向?qū)ο鬅o法簡(jiǎn)單的解決這個(gè)問題,一個(gè)關(guān)注點(diǎn)是面向所有而不是單一的類迅细,不受類的邊界的約束淘邻,因此OOP無法將關(guān)注點(diǎn)聚焦來解決宾舅,只能分散到各個(gè)類中。
AOP(面向切面編程)則是針對(duì)業(yè)務(wù)處理過程中的切面進(jìn)行提取扶平,它所面對(duì)的是處理過程中的某個(gè)步驟或階段蔬蕊,以獲得邏輯過程中各部分之間低耦合性的隔離效果岸夯。這兩種設(shè)計(jì)思想在目標(biāo)上有著本質(zhì)的差異。
AOP并不是與OOP對(duì)立的勉吻,而是為了彌補(bǔ)OOP的不足旅赢。OOP解決了豎向的問題煮盼,AOP則解決橫向的問題。因?yàn)橛辛薃OP我們的調(diào)試和監(jiān)控就變得簡(jiǎn)單清晰踩娘。

簡(jiǎn)單的來講,AOP是一種:可以在不改變?cè)瓉泶a的基礎(chǔ)上雷绢,通過“動(dòng)態(tài)注入”代碼翘紊,來改變?cè)瓉韴?zhí)行結(jié)果的技術(shù)藐唠。

(2)AOP主要應(yīng)用場(chǎng)景

日志記錄,性能統(tǒng)計(jì)踪宠,安全控制柳琢,事務(wù)處理润脸,異常處理等等毙驯。

(3)主要目標(biāo)

將日志記錄,性能統(tǒng)計(jì)涩馆,安全控制允坚,事務(wù)處理稠项,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對(duì)這些行為的分離活逆,我們希望可以將它們獨(dú)立到非指導(dǎo)業(yè)務(wù)邏輯的方法中拗胜,進(jìn)而改變這些行為的時(shí)候不影響業(yè)務(wù)邏輯的代碼埂软。

[圖片上傳失敗...(image-f77692-1591768775090)]

上圖是一個(gè)APP模塊結(jié)構(gòu)示例,按照照OOP的思想劃分為“視圖交互”所灸,“業(yè)務(wù)邏輯”爬立,“網(wǎng)絡(luò)”等三個(gè)模塊,而現(xiàn)在假設(shè)想要對(duì)所有模塊的每個(gè)方法耗時(shí)(性能監(jiān)控模塊)進(jìn)行統(tǒng)計(jì)抡秆。這個(gè)性能監(jiān)控模塊的功能就是需要橫跨并嵌入眾多模塊里的吟策,這就是典型的AOP的應(yīng)用場(chǎng)景踊挠。

AOP的目標(biāo)是把這些橫跨并嵌入眾多模塊里的功能(如監(jiān)控每個(gè)方法的性能) 集中起來冲杀,放到一個(gè)統(tǒng)一的地方來控制和管理权谁。如果說,OOP如果是把問題劃分到單個(gè)模塊的話沪猴,那么AOP就是把涉及到眾多模塊的某一類問題進(jìn)行統(tǒng)一管理痹扇。

對(duì)比:

功能 OOP AOP
增加日志 所有功能模塊單獨(dú)添加担租,容易出錯(cuò) 能夠?qū)⑼粋€(gè)關(guān)注點(diǎn)聚焦在一處解決
修改日志 功能代碼分散抵怎,不方便調(diào)試 能夠?qū)崿F(xiàn)一處修改奋救,處處生效

例如:在不改變 main 方法的同時(shí)通過代碼注入的方式達(dá)到目的

/**
 * Before
 */
public class Test {
    public static void main(String[] args) {
        // do something
    }
}

/**
 * After
 */
public class Test {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        // do something
        long end = System.currentTimeMillis() - start;
    }
}

二、AOP代碼注入時(shí)機(jī)

代碼注入主要注解機(jī)制反惕,根據(jù)注解時(shí)機(jī)的不同尝艘,主要分為運(yùn)行時(shí)、加載時(shí)和編譯時(shí)姿染。

運(yùn)行時(shí):你的代碼對(duì)增強(qiáng)代碼的需求很明確背亥,比如,必須使用動(dòng)態(tài)代理(這可以說并不是真正的代碼注入)。
加載時(shí):當(dāng)目標(biāo)類被Dalvik或者ART加載的時(shí)候修改才會(huì)被執(zhí)行隘梨。這是對(duì)Java字節(jié)碼文件或者Android的dex文件進(jìn)行的注入操作程癌。
編譯時(shí):在打包發(fā)布程序之前轴猎,通過向編譯過程添加額外的步驟來修改被編譯的類嵌莉。aspect切面編程正是運(yùn)用到編譯時(shí)

三、AOP的幾種實(shí)現(xiàn)方式

  • Java 中的動(dòng)態(tài)代理捻脖,運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建 Proxy 類實(shí)例
  • APT锐峭,注解處理器,編譯時(shí)生成 .java 代碼
  • Javassist for Android:一個(gè)移植到Android平臺(tái)的非常知名的操縱字節(jié)碼的java庫可婶,對(duì) class 字節(jié)碼進(jìn)行修改
  • AspectJ:和Java語言無縫銜接的面向切面的編程的擴(kuò)展工具(可用于Android)沿癞。

四,Android中使用 AspectJ

代表項(xiàng)目:Hugo(打印每個(gè)方法的執(zhí)行時(shí)間) sa-sdk-android(全埋點(diǎn)技術(shù))

(1)原理

AspectJ 意思就是Java的Aspect,Java的AOP矛渴。它的核心是ajc(編譯? aspectjtools)和 weaver(織入? aspectjweaver)椎扬。

ajc編譯?:基于Java編譯?之上的,它是用來編譯.aj文件具温,aspectj在Java編譯?的基礎(chǔ)上增加了一些它自己的關(guān)鍵字和方法蚕涤。因此,ajc也可以編譯Java代碼铣猩。

weaver織入?:為了在java編譯?上使用AspectJ而不依賴于Ajc編譯?揖铜,aspectJ 5出現(xiàn)了 @AspectJ,使用注釋的方式編寫AspectJ代碼达皿,可以在任何Java編譯?上使用天吓。
由于AndroidStudio默認(rèn)是沒有ajc編譯?的,所以在Android中使用@AspectJ來編寫峦椰。它在代碼的編譯期間掃描目標(biāo)程序龄寞,根據(jù)切點(diǎn)(PointCut)匹配,將開發(fā)者編寫的Aspect程序編織(Weave)到目標(biāo)程序的.class文件中,對(duì)目標(biāo)程序作了重構(gòu)(重構(gòu)單位是JoinPoint)汤功,目的就是建立目標(biāo)程序與Aspect程序的連接(獲得執(zhí)行的對(duì)象物邑、方法、參數(shù)等上下文信息)冤竹,從而達(dá)到AOP的目的拂封。

(2)AspectJ 術(shù)語

切面(Aspect):一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)實(shí)現(xiàn)可能另外橫切多個(gè)對(duì)象鹦蠕。其實(shí)就是共有功能的實(shí)現(xiàn)冒签。如日志切面、權(quán)限切面钟病、事務(wù)切面等萧恕。

通知(Advice):是切面的具體實(shí)現(xiàn)刚梭。以目標(biāo)方法為參照點(diǎn),根據(jù)放置的地方不同票唆,可分為

  1. 前置通知(Before)朴读、
  2. 后置通知(AfterReturning)、
  3. 異常通知(AfterThrowing)走趋、
  4. 最終通知(After)
  5. 環(huán)繞通知(Around)5種衅金。

在實(shí)際應(yīng)用中通常是切面類中的一個(gè)方法,具體屬于哪類通知由配置指定的簿煌。

切入點(diǎn)(Pointcut):用于定義通知應(yīng)該切入到哪些連接點(diǎn)上氮唯。不同的通知通常需要切入到不同的連接點(diǎn)上,這種精準(zhǔn)的匹配是由切入點(diǎn)的正則表達(dá)式來定義的姨伟。
連接點(diǎn)(JoinPoint):就是程序在運(yùn)行過程中能夠插入切面的地點(diǎn)惩琉。例如,方法調(diào)用夺荒、異常拋出或字段修改等瞒渠。

目標(biāo)對(duì)象(Target Object):包含連接點(diǎn)的對(duì)象,也被稱作被通知或被代理對(duì)象技扼。這些對(duì)象中已經(jīng)只剩下干干凈凈的核心業(yè)務(wù)邏輯代碼了伍玖,所有的共有功能等代碼則是等待AOP容器的切入。

AOP代理(AOP Proxy):將通知應(yīng)用到目標(biāo)對(duì)象之后被動(dòng)態(tài)創(chuàng)建的對(duì)象淮摔∷骄冢可以簡(jiǎn)單地理解為始赎,代理對(duì)象的功能等于目標(biāo)對(duì)象的核心業(yè)務(wù)邏輯功能加上共有功能和橙。代理對(duì)象對(duì)于使用者而言是透明的,是程序運(yùn)行過程中的產(chǎn)物造垛。

編織(Weaving):將切面應(yīng)用到目標(biāo)對(duì)象從而創(chuàng)建一個(gè)新的代理對(duì)象的過程魔招。這個(gè)過程可以發(fā)生在編譯期、類裝載期及運(yùn)行期五辽,當(dāng)然不同的發(fā)生點(diǎn)有著不同的前提條件办斑。譬如發(fā)生在編譯期的話,就要求有一個(gè)支持這種AOP實(shí)現(xiàn)的特殊編譯器(如AspectJ編譯器)杆逗;

發(fā)生在類裝載期乡翅,就要求有一個(gè)支持AOP實(shí)現(xiàn)的特殊類裝載器;只有發(fā)生在運(yùn)行期罪郊,則可直接通過Java語言的反射機(jī)制與動(dòng)態(tài)代理機(jī)制來動(dòng)態(tài)實(shí)現(xiàn)(如搖一搖)蠕蚜。

引入(Introduction):添加方法或字段到被通知的類。

(3)在Android項(xiàng)目中使用AspectJ
  • gradle配置的方式:引入AspectJ是有點(diǎn)復(fù)雜的悔橄,需要引入大量的gradle命令配置有點(diǎn)麻煩靶累,在build文件中添加了一些腳本腺毫,文章出處:https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
  • 使用 gradle 插件(也是對(duì) gradle 命令進(jìn)行了包裝):Jake Wharton 大神的 hugo 項(xiàng)目(一款日志打印的插件)

上海滬江團(tuán)隊(duì)的 gradle_plugin_android_aspectjx 一個(gè)基于AspectJ并在此基礎(chǔ)上擴(kuò)展出來可應(yīng)用于Android開發(fā)平臺(tái)的AOP框架,可作用于java源碼挣柬,class文件及jar包潮酒,同時(shí)支持kotlin的應(yīng)用。

AOP的用處非常廣邪蛔,從spring到Android急黎,各個(gè)地方都有使用,特別是在后端侧到,Spring中已經(jīng)使用的非常方便了叁熔,而且功能非常強(qiáng)大,但是在Android中床牧,AspectJ的實(shí)現(xiàn)是略閹割的版本荣回,并不是所有功能都支持,但對(duì)于一般的客戶端開發(fā)來說戈咳,已經(jīng)完全足夠用了心软。

(4)以 AspectJX 接入說明
  • 首先,需要在項(xiàng)目根目錄的build.gradle中增加依賴:
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
    }
}
  • 然后module項(xiàng)目的 build.gradle 中加入 AspectJ 的依賴:
apply plugin: 'android-aspectjx'
dependencies {
        compile 'org.aspectj:aspectjrt:1.8.+'
    }

aspectjx {
    //排除所有package路徑中包含`android.support`的class文件及庫(jar文件)
    exclude 'org.apache.httpcomponents'
    exclude 'android.support'
}
@Aspect
public class AspectTest {

    private static final String TAG = "xuyisheng";

    @Before("execution(* android.app.Activity.on**(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore: " + key);
    }

    @After("execution(* android.app.Activity.on**(..))")
    public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodAfter: " + key);
    }

    @Around("execution(* android.app.Activity.on**(..))")
    public void onActivityMethodAfter(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore: " + key);
        joinPoint.proceed();
        Log.e(TAG, "onActivityMethodAfter: " + key);
    }
}

在類的最開始,我們使用 @Aspect 注解來定義這樣一個(gè)AspectJ文件踏堡,編譯器在編譯的時(shí)候猎唁,就會(huì)自動(dòng)去解析,并不需要主動(dòng)去調(diào)用AspectJ類里面的代碼顷蟆。

具體的切面表達(dá)式可以參考execution表達(dá)式解析

(5)編織速度優(yōu)化建議
  • 盡量使用精確的匹配規(guī)則,降低匹配時(shí)間诫隅。
  • 排除不需要掃描的包。

通過這種方式編譯后帐偎,我們來看下生成的代碼是怎樣的逐纬。AspectJ的原理實(shí)際上是在編譯的時(shí)候,根據(jù)一定的規(guī)則解析削樊,然后插入一些代碼豁生,通過aspectj生成的代碼,會(huì)在Build目錄下:

[圖片上傳失敗...(image-fac24-1591768775090)]

四漫贞、總結(jié):

Aspectj:
  • AspectJ除了hook之外甸箱,AspectJ還可以為目標(biāo)類添加變量,接口迅脐。另外芍殖,AspectJ也有抽象,繼承等各種更高級(jí)的玩法仪际。它能夠在編譯期間直接修改源代碼生成class围小。
  • AspectJ語法比較多昵骤,但是掌握幾個(gè)簡(jiǎn)單常用的,就能實(shí)現(xiàn)絕大多數(shù)切片肯适,完全兼容Java(純Java語言開發(fā)变秦,然后使用AspectJ注解,簡(jiǎn)稱@AspectJ框舔。)

Kotlin無法生效的問題請(qǐng)參考此處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蹦玫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子刘绣,更是在濱河造成了極大的恐慌樱溉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纬凤,死亡現(xiàn)場(chǎng)離奇詭異福贞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)停士,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門挖帘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恋技,你說我怎么就攤上這事拇舀。” “怎么了蜻底?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵骄崩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我薄辅,道長(zhǎng)要拂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任长搀,我火速辦了婚禮宇弛,結(jié)果婚禮上鸡典,老公的妹妹穿的比我還像新娘源请。我一直安慰自己,他們只是感情好彻况,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布谁尸。 她就那樣靜靜地躺著,像睡著了一般纽甘。 火紅的嫁衣襯著肌膚如雪良蛮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天悍赢,我揣著相機(jī)與錄音决瞳,去河邊找鬼货徙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛皮胡,可吹牛的內(nèi)容都是我干的痴颊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼屡贺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蠢棱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起甩栈,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤泻仙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后量没,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玉转,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年殴蹄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冤吨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饶套,死狀恐怖漩蟆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妓蛮,我是刑警寧澤怠李,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站蛤克,受9級(jí)特大地震影響捺癞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜构挤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一髓介、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筋现,春花似錦唐础、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洒沦,卻和暖如春豹绪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背申眼。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工瞒津, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝉衣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓巷蚪,卻偏偏與公主長(zhǎng)得像买乃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钓辆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345