AspectJ 在APM上的應(yīng)用(二)

AspectJ 語法

上篇文章介紹了 AspectJ 的基本概念,這篇文章詳細(xì)分析 AspectJ 基于注解開發(fā)方式的語法劫映。

Join Point

Join Point 表示連接點(diǎn)祖今,即 AOP 可織入代碼的點(diǎn)膏斤,下表列出了 AspectJ 的所有連接點(diǎn):

Join Point 說明
Method call 方法被調(diào)用
Method execution 方法執(zhí)行
Constructor call 構(gòu)造函數(shù)被調(diào)用
Constructor execution 構(gòu)造函數(shù)執(zhí)行
Field get 讀取屬性
Field set 寫入屬性
Pre-initialization 與構(gòu)造函數(shù)有關(guān)敞映,很少用到
Initialization 與構(gòu)造函數(shù)有關(guān)冕末,很少用到
Static initialization static 塊初始化
Handler 異常處理
Advice execution 所有 Advice 執(zhí)行

Pointcuts

Pointcuts 是具體的切入點(diǎn)攻询,可以確定具體織入代碼的地方钧栖,基本的 Pointcuts 是和 Join Point 相對應(yīng)的桐经。

Join Point Pointcuts syntax
Method call call(MethodPattern)
Method execution execution(MethodPattern)
Constructor call call(ConstructorPattern)
Constructor execution execution(ConstructorPattern)
Field get get(FieldPattern)
Field set set(FieldPattern)
Pre-initialization initialization(ConstructorPattern)
Initialization preinitialization(ConstructorPattern)
Static initialization staticinitialization(TypePattern)
Handler handler(TypePattern)
Advice execution adviceexcution()

除了上面與 Join Point 對應(yīng)的選擇外誓沸,Pointcuts 還有其他選擇方法:

Pointcuts synatx 說明
within(TypePattern) 符合 TypePattern 的代碼中的 Join Point
withincode(MethodPattern) 在某些方法中的 Join Point
withincode(ConstructorPattern) 在某些構(gòu)造函數(shù)中的 Join Point
cflow(Pointcut) Pointcut 選擇出的切入點(diǎn) P 的控制流中的所有 Join Point痊焊,包括 P 本身
cflowbelow(Pointcut) Pointcut 選擇出的切入點(diǎn) P 的控制流中的所有 Join Point蕾管,不包括 P 本身
this(Type or Id) Join Point 所屬的 this 對象是否 instanceOf Type 或者 Id 的類型
target(Type or Id) Join Point 所在的對象(例如 call 或 execution 操作符應(yīng)用的對象)是否 instanceOf Type 或者 Id 的類型
args(Type or Id, …) 方法或構(gòu)造函數(shù)參數(shù)的類型
if(BooleanExpression) 滿足表達(dá)式的 Join Point,表達(dá)式只能使用靜態(tài)屬性菩暗、Pointcuts 或 Advice 暴露的參數(shù)掰曾、thisJoinPoint 對象

Pointcut 表達(dá)式還可以 !停团、&&旷坦、|| 來組合,!Pointcut 選取不符合 Pointcut 的 Join Point佑稠,Pointcut0 && Pointcut1 選取符合 Pointcut0 和 Pointcut1 的 Join Point秒梅,Pointcut0 || Pointcut1 選取符合 Pointcut0 或 Pointcut1 的 Join Point。

上面 Pointcuts 的語法中涉及到一些 Pattern舌胶,下面是這些 Pattern 的規(guī)則捆蜀,[]里的內(nèi)容是可選的:

Pattern 規(guī)則
MethodPattern [!][@Annotation] [public,protected,private][static] [final] 返回值類型 [類名.]方法名(參數(shù)類型列表) [throws 異常類型]
ConstructorPattern [!][@Annotation] [public,protected,private][final] [類名.]new(參數(shù)類型列表) [throws 異常類型]
FieldPattern [!][@Annotation] [public,protected,private][static] [final] 屬性類型 [類名.]屬性名
TypePattern 其他 Pattern 涉及到的類型規(guī)則也是一樣,可以使用 ‘!’幔嫂、’‘辆它、’..’、’+’履恩,’!’ 表示取反锰茉,’‘ 匹配除 . 外的所有字符串,’*’ 單獨(dú)使用事表示匹配任意類型切心,’..’ 匹配任意字符串飒筑,’..’ 單獨(dú)使用時表示匹配任意長度任意類型片吊,’+’ 匹配其自身及子類,還有一個 ‘…’表示不定個數(shù)

TypePattern 也可以使用 &&协屡、|| 操作符定鸟,其他 Pointcut 更詳細(xì)的語法說明,見官網(wǎng)文檔 Pointcuts Language Semantics著瓶。

Pointcut 示例

execution(void void android.view.View.OnClickListener+.onClick(..)) – OnClickListener 及其子類的 onClick 方法執(zhí)行時

call(@retrofit2.http.GET public com.johnny.core.http..(..)) – ‘com.johnny.core.http’開頭的包下面的所有 GET 方法調(diào)用時

call(android.support.v4.app.Fragment+.new(..)) – support 包中的 Fragment 及其子類的構(gòu)造函數(shù)調(diào)用時

set(@Inject ) – 寫入所有 @Inject 注解修飾的屬性時

handler(IOException) && within(com.johnny.core.http..) – ‘com.johnny.core.http’開頭的包代碼中處理 IOException 時

execution(void setUserVisibleHint(..)) && target(android.support.v4.app.Fragment) && args(boolean) – 執(zhí)行 Fragment 及其子類的 setUserVisibleHint(boolean) 方法時

execution(void Foo.foo(..)) && cflowbelow(execution(void Foo.foo(..))) – 執(zhí)行 Foo.foo() 方法中再遞歸執(zhí)行 Foo.foo() 時

Pointcut 聲明

Pointcuts 可以在普通的 class 或 Aspect class 中定義,由 org.aspectj.lang.annotation.Pointcut 注解修飾的方法聲明啼县,方法返回值只能是 void材原。@Pointcut 修飾的方法只能由空的方法實(shí)現(xiàn)而且不能有 throws 語句,方法的參數(shù)和 pointcut 中的參數(shù)相對應(yīng)季眷。

看下面這個例子:

@Aspect
class Test {
    @Pointcut("execution(void Foo.foo(..)")
    public void executFoo() {}

    @Pointcut("executFoo() && cflowbelow(executFoo()) && target(foo) && args(i)")
    public void loopExecutFoo(Foo foo, int i) {}
}

if() 表達(dá)式

在基于 AspectJ 注解的開發(fā)方式中余蟹,if(...) 表達(dá)式的用法與其他的選擇操作符不同,在 @Pointcut 的語句中 if 表達(dá)式只能是if()子刮、if(true)if(false)威酒,而且 @Pointcut 方法必須為 public static boolean,方法體內(nèi)就是 if 表達(dá)式的內(nèi)容挺峡,可以使用暴露的參數(shù)葵孤、靜態(tài)屬性、JoinPoint橱赠、JoinPointStaticPart尤仍、JoinPoint.EnclosingStaticPart。

static int COUNT = 0;

@Pointcut("call(* *.*(int)) && args(i) && if()")
public static boolean someCallWithIfTest(int i, JoinPoint jp, JoinPoint.EnclosingStaticPart esjp) {
    // any legal Java expression...
    return i > 0
        && jp.getSignature().getName.startsWith("doo")
        && esjp.getSignature().getName().startsWith("test")
        && COUNT++ < 10;
}

if() 表達(dá)式使用的比較少狭姨,大致了解下就可以了宰啦。

target() 與 this()

target() 與 this() 很容易混淆,target() 是指 Pointcut 選取的 Join Point 的所有者饼拍;this() 是指 Pointcut 選取的 Join Point 的調(diào)用的所有者赡模。簡單地說就是,PointcutA 選取的是 methodA师抄,那么 target 就是 methodA() 這個方法的對象漓柑,而 this 就是 methodA 被調(diào)用時所在類的對象。

看下面這個例子:

class Test {
    public void test() {...}
}

class A {
    ...
    test1.test(); // test() 在 a 的某方法中調(diào)用
    ...
}

@Aspect
class TestAspect {
    @Pointcut("call(void Test.test()) && target(Test)")
    public test1() {}

    @Pointcut("call(void Test.test()) && this(A)")
    public test2() {}
}

上面代碼中 test1.test() 方法屬于 test1 對象叨吮,所以 target 為 test1欺缘,而該方法在 a 對象的方法中調(diào)用,所以 this 為 a挤安。

Advice

Advice 是在切入點(diǎn)上織入的代碼谚殊,在 AspectJ 中有五種類型:Before、After蛤铜、AfterReturning嫩絮、AfterThrowing丛肢、Around。

Advice 說明
@Before 在執(zhí)行 Join Point 之前
@After 在執(zhí)行 Join Point 之后剿干,包括正常的 return 和 throw 異常
@AfterReturning Join Point 為方法調(diào)用且正常 return 時蜂怎,不指定返回類型時匹配所有類型
@AfterThrowing Join Point 為方法調(diào)用且拋出異常時,不指定異常類型時匹配所有類型
@Around 替代 Join Point 的代碼置尔,如果要執(zhí)行原來代碼的話杠步,要使用 ProceedingJoinPoint.proceed()

注意: After 和 Before 沒有返回值,但是 Around 的目標(biāo)是替代原 Join Point 的榜轿,所以它一般會有返回值幽歼,而且返回值的類型需要匹配被選中的 Join Point 的代碼。而且不能和其他 Advice 一起使用谬盐,如果在對一個 Pointcut 聲明 Around 之后還聲明 Before 或者 After 則會失效甸私。

Advice 注解修改的方法必須為 public,Before飞傀、After皇型、AfterReturning高帖、AfterThrowing 四種類型修飾的方法返回值也必須為 void诱篷,Advice 需要使用 JoinPoint、JoinPointStaticPart胀溺、JoinPoint.EnclosingStaticPart 時幢痘,要在方法中聲明為額外的參數(shù)寡键,@Around 方法可以使用 ProceedingJoinPoint,用以調(diào)用 proceed() 方法雪隧。

看下面幾個示例西轩,進(jìn)一步了解 Advice 用法:

@Before("call(* *.*(..)) && this(foo)")
public void callFromFoo(Foo foo) {
    Log.d(TAG, "call from Foo:" + foo);
}

@AfterReturning(pointcut="call(Foo+.new(..))", returning="f")
public void itsAFoo(Foo f, JoinPoint thisJoinPoint) {
    // ...
}

@Around("call(* setAge(..)) && args(i)")
public Object twiceAsOld(int i, ProceedingJoinPoint thisJoinPoint) {
    return thisJoinPoint.proceed(new Object[]{i * 2}); // 原來參數(shù)乘以 2
}

注:Handler Pointcut 不能使用 After 和 Around。

Aspect

Aspect 就是 AOP 中的關(guān)鍵單位 – 切面脑沿,我們一般會把相關(guān) Pointcut 和 Advice 放在一個 Aspect 類中藕畔,在基于 AspectJ 注解開發(fā)方式中只需要在類的頭部加上 @Aspect 注解即可,@Aspect 不能修飾接口。

例如庄拇,定義一個 LogAspect注服,在需要的 Join Point 上加上打印日志的 Advice,這樣就形成了一個 LogAspect 的切面措近,在編譯期會將代碼織入到相應(yīng)的方法中溶弟,但是在編碼中只需要關(guān)注 LogAspect 即可。

在多個切入點(diǎn)織入 Advice 代碼時瞭郑,會涉及到 Aspect 對象實(shí)例的問題辜御,因?yàn)?Advice 代碼是 Aspect 的方法。一般情況下屈张,我們使用的都是單例的 Aspect擒权,即所有 Advice 代碼使用的都是同一個 Aspect 對象實(shí)例袱巨。

Singleton Aspect

文章中代碼示例都是單例的 Aspect,這也是最常見的碳抄,定義方式為:@Aspect 或者 @Aspect()愉老。

編譯期,ajc 編譯期會給單例的切面加上靜態(tài)的 aspectOf() 方法來獲取單例實(shí)例剖效,還有一個 hasAspect() 靜態(tài)方法判斷實(shí)例是否初始化嫉入。假設(shè) FragmentAspect 有 Advice 方法 advice1(),織入切入點(diǎn)的代碼就是 FragmentAspect.aspectOf().advice1()璧尸。

Per-object, Per-cflow Aspect 等

除了單例 Aspect 外咒林,還可以根據(jù) Join Point 的相應(yīng)對象、控制流逗宁、所在類型產(chǎn)生不同的實(shí)例。

定義方式為:@Aspect("perthis|pertarget|percflow|percflowbelow(Pointcut) | pertypewithin(TypePattern)")梦湘,因?yàn)椴怀R娤箍牛跃秃唵谓榻B下,想進(jìn)一步了解請看 Aspects Language Semantics 捌议。

Inter-type Declarations

上面提到的都是 Pointcut 和 Advice 都是在類本身結(jié)構(gòu)不變的情況下織入代碼哼拔,AspectJ 的 Inter-type Declarations 可以修改類的結(jié)構(gòu),給類添加方法或者屬性瓣颅,讓類繼承多個類或者實(shí)現(xiàn)多個接口倦逐。但是基于 AspectJ 注解開發(fā)方式因?yàn)榧夹g(shù)原因,目前只能讓類實(shí)現(xiàn)多個接口宫补,通俗的說法就是給類添加接口檬姥,也添加了接口的方法。

給類添加接口粉怕,實(shí)際通過實(shí)現(xiàn)了該接口的代理來完成對原類型的替換健民,所以需要提供實(shí)現(xiàn)了該接口的實(shí)現(xiàn)完成代理中接口的具體行為,不然只是增加接口贫贝,沒有接口實(shí)現(xiàn)沒什么用處秉犹。@DeclareMixin 就是用來確定接口的默認(rèn)實(shí)現(xiàn),綁定一個產(chǎn)生該接口的默認(rèn)實(shí)現(xiàn)的工廠方法稚晚,以該接口為返回類型崇堵。

看下面代碼,給 Fragment 添加 Title 接口:

public interface Title {
    String getTitle();
}

public class TitleImpl implements Title {
    @Override
    public String getTitle() {
        return "Test";
    }
}

@Aspect
public class FragmentAspect {
    @DeclareMixin("android.support.v4.app.Fragment")
    public static Title createDelegate() {
        return new TitleImpl();
    }
}

上面代碼可以給 Fragment 添加了 Title 接口客燕,如果@DeclareMixin("android.support.v4.app.*")的話鸳劳,則給 app 下所有類添加 Title 接口,之后通過正常的類型轉(zhuǎn)換來訪問 Title 接口:

String title = ((Title) fragment).getTitle(); // 返回 Test 字符串

也可以將原對象作為接口默認(rèn)實(shí)現(xiàn)的參數(shù)也搓,這樣就可以根據(jù) fragment 的屬性返回不同的 title :

public class TitleImpl implements Title {

    private final String title;

    public TitleImpl(Fragment fragment) {
        title = fragment.getClass().getSimpleName();
    }

    @Override
    public String getTitle() {
        return title;
    }
}

@Aspect
public class FragmentAspect {
    @DeclareMixin("android.support.v4.app.Fragment")
    public static Title createDelegate(Fragment fragment) {
        return new TitleImpl(fragment);
    }
}

上面代碼返回 fragment 的類名作為 title棍辕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暮现,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子楚昭,更是在濱河造成了極大的恐慌栖袋,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抚太,死亡現(xiàn)場離奇詭異塘幅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尿贫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門电媳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庆亡,你說我怎么就攤上這事匾乓。” “怎么了又谋?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵拼缝,是天一觀的道長。 經(jīng)常有香客問我彰亥,道長咧七,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任任斋,我火速辦了婚禮继阻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘废酷。我一直安慰自己瘟檩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布澈蟆。 她就那樣靜靜地躺著芒帕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丰介。 梳的紋絲不亂的頭發(fā)上背蟆,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音哮幢,去河邊找鬼带膀。 笑死,一個胖子當(dāng)著我的面吹牛橙垢,可吹牛的內(nèi)容都是我干的垛叨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗽元!你這毒婦竟也來了敛纲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤剂癌,失蹤者是張志新(化名)和其女友劉穎淤翔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佩谷,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旁壮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谐檀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抡谐。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖桐猬,靈堂內(nèi)的尸體忽然破棺而出麦撵,到底是詐尸還是另有隱情,我是刑警寧澤溃肪,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布免胃,位于F島的核電站,受9級特大地震影響乍惊,放射性物質(zhì)發(fā)生泄漏杜秸。R本人自食惡果不足惜放仗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一润绎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诞挨,春花似錦莉撇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至银室,卻和暖如春涂佃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜈敢。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工辜荠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抓狭。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓伯病,卻偏偏與公主長得像,于是被迫代替她去往敵國和親否过。 傳聞我的和親對象是個殘疾皇子午笛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容