AspectJ in Android (二)榜掌,AspectJ 語(yǔ)法

AspectJ in Android 系列:

AspectJ in Android (一),AspectJ 基礎(chǔ)概念

AspectJ in Android (二)肥惭,AspectJ 語(yǔ)法

AspectJ in Android (三)么伯,AspectJ 兩種用法以及常見問題

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

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 相對(duì)應(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 對(duì)應(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 對(duì)象是否 instanceOf Type 或者 Id 的類型
target(Type or Id) Join Point 所在的對(duì)象(例如 call 或 execution 操作符應(yīng)用的對(duì)象)是否 instanceOf Type 或者 Id 的類型
args(Type or Id, ...) 方法或構(gòu)造函數(shù)參數(shù)的類型
if(BooleanExpression) 滿足表達(dá)式的 Join Point准验,表達(dá)式只能使用靜態(tài)屬性、Pointcuts 或 Advice 暴露的參數(shù)廷没、thisJoinPoint 對(duì)象

Pointcut 表達(dá)式還可以 糊饱!、&&颠黎、|| 來組合另锋,!Pointcut 選取不符合 Pointcut 的 Join Point,Pointcut0 && Pointcut1 選取符合 Pointcut0 和 Pointcut1 的 Join Point狭归,Pointcut0 || Pointcut1 選取符合 Pointcut0 或 Pointcut1 的 Join Point夭坪。

上面 Pointcuts 的語(yǔ)法中涉及到一些 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í)表示匹配任意長(zhǎng)度任意類型勾哩,'+' 匹配其自身及子類股耽,還有一個(gè) '...'表示不定個(gè)數(shù)

TypePattern 也可以使用 &&、|| 操作符钳幅,其他 Pointcut 更詳細(xì)的語(yǔ)法說明物蝙,見官網(wǎng)文檔 Pointcuts Language Semantics

Pointcut 示例

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

call(@retrofit2.http.GET public * com.johnny.core.http..*(..)) -- 'com.johnny.core.http'開頭的包下面的所有 GET 方法調(diào)用時(shí)

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

set(@Inject * *) -- 寫入所有 @Inject 注解修飾的屬性時(shí)

handler(IOException) && within(com.johnny.core.http..) -- 'com.johnny.core.http'開頭的包代碼中處理 IOException 時(shí)

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

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

Pointcut 聲明

Pointcuts 可以在普通的 class 或 Aspect class 中定義敢艰,由 org.aspectj.lang.annotation.Pointcut 注解修飾的方法聲明诬乞,方法返回值只能是 void。@Pointcut 修飾的方法只能由空的方法實(shí)現(xiàn)而且不能有 throws 語(yǔ)句钠导,方法的參數(shù)和 pointcut 中的參數(shù)相對(duì)應(yīng)震嫉。

看下面這個(gè)例子:

@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 的語(yǔ)句中 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)用的所有者。簡(jiǎn)單地說就是提前,PointcutA 選取的是 methodA吗货,那么 target 就是 methodA() 這個(gè)方法的對(duì)象,而 this 就是 methodA 被調(diào)用時(shí)所在類的對(duì)象岖研。

看下面這個(gè)例子:

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 對(duì)象卿操,所以 target 為 test1警检,而該方法在 a 對(duì)象的方法中調(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 時(shí)鸽凶,不指定返回類型時(shí)匹配所有類型
@AfterThrowing Join Point 為方法調(diào)用且拋出異常時(shí)币砂,不指定異常類型時(shí)匹配所有類型
@Around 替代 Join Point 的代碼,如果要執(zhí)行原來代碼的話玻侥,要使用 ProceedingJoinPoint.proceed()

注意: After 和 Before 沒有返回值决摧,但是 Around 的目標(biāo)是替代原 Join Point 的,所以它一般會(huì)有返回值凑兰,而且返回值的類型需要匹配被選中的 Join Point 的代碼掌桩。而且不能和其他 Advice 一起使用,如果在對(duì)一個(gè) Pointcut 聲明 Around 之后還聲明 Before 或者 After 則會(huì)失效姑食。

Advice 注解修改的方法必須為 public波岛,Before、After音半、AfterReturning则拷、AfterThrowing 四種類型修飾的方法返回值也必須為 void,Advice 需要使用 JoinPoint曹鸠、JoinPointStaticPart隔躲、JoinPoint.EnclosingStaticPart 時(shí),要在方法中聲明為額外的參數(shù)物延,@Around 方法可以使用 ProceedingJoinPoint宣旱,用以調(diào)用 proceed() 方法。

看下面幾個(gè)示例叛薯,進(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)鍵單位 -- 切面,我們一般會(huì)把相關(guān) Pointcut 和 Advice 放在一個(gè) Aspect 類中耗溜,在基于 AspectJ 注解開發(fā)方式中只需要在類的頭部加上 @Aspect 注解即可,@Aspect 不能修飾接口组力。

例如,定義一個(gè) LogAspect抖拴,在需要的 Join Point 上加上打印日志的 Advice燎字,這樣就形成了一個(gè) LogAspect 的切面腥椒,在編譯期會(huì)將代碼織入到相應(yīng)的方法中,但是在編碼中只需要關(guān)注 LogAspect 即可候衍。

在多個(gè)切入點(diǎn)織入 Advice 代碼時(shí)笼蛛,會(huì)涉及到 Aspect 對(duì)象實(shí)例的問題,因?yàn)?Advice 代碼是 Aspect 的方法蛉鹿。一般情況下滨砍,我們使用的都是單例的 Aspect,即所有 Advice 代碼使用的都是同一個(gè) Aspect 對(duì)象實(shí)例妖异。

Singleton Aspect

文章中代碼示例都是單例的 Aspect惋戏,這也是最常見的,定義方式為:@Aspect 或者 @Aspect()他膳。

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

Per-object, Per-cflow Aspect 等

除了單例 Aspect 外散罕,還可以根據(jù) Join Point 的相應(yīng)對(duì)象分歇、控制流、所在類型產(chǎn)生不同的實(shí)例欧漱。

定義方式為:@Aspect("perthis|pertarget|percflow|percflowbelow(Pointcut) | pertypewithin(TypePattern)")职抡,因?yàn)椴怀R姡跃秃?jiǎn)單介紹下误甚,想進(jìn)一步了解請(qǐng)看 Aspects Language Semantics 缚甩。

Inter-type Declarations

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

給類添加接口厉熟,實(shí)際通過實(shí)現(xiàn)了該接口的代理來完成對(duì)原類型的替換,所以需要提供實(shí)現(xiàn)了該接口的實(shí)現(xiàn)完成代理中接口的具體行為较幌,不然只是增加接口揍瑟,沒有接口實(shí)現(xiàn)沒什么用處。@DeclareMixin 就是用來確定接口的默認(rèn)實(shí)現(xiàn)乍炉,綁定一個(gè)產(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 字符串

也可以將原對(duì)象作為接口默認(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纯续。

關(guān)于 AspectJ 基于注解開發(fā)方式的語(yǔ)法就講到這里随珠,下一篇文章根據(jù)實(shí)際例子介紹 AspectJ 的常見用法灭袁。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窗看,一起剝皮案震驚了整個(gè)濱河市茸歧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌显沈,老刑警劉巖软瞎,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拉讯,居然都是意外死亡涤浇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門魔慷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來只锭,“玉大人,你說我怎么就攤上這事院尔◎哒梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵邀摆,是天一觀的道長(zhǎng)纵顾。 經(jīng)常有香客問我,道長(zhǎng)栋盹,這世上最難降的妖魔是什么施逾? 我笑而不...
    開封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮例获,結(jié)果婚禮上汉额,老公的妹妹穿的比我還像新娘。我一直安慰自己躏敢,他們只是感情好闷愤,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著件余,像睡著了一般讥脐。 火紅的嫁衣襯著肌膚如雪遭居。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天旬渠,我揣著相機(jī)與錄音俱萍,去河邊找鬼。 笑死告丢,一個(gè)胖子當(dāng)著我的面吹牛枪蘑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岖免,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼岳颇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了颅湘?” 一聲冷哼從身側(cè)響起话侧,我...
    開封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闯参,沒想到半個(gè)月后瞻鹏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹿寨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年新博,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脚草。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赫悄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玩讳,到底是詐尸還是另有隱情涩蜘,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布熏纯,位于F島的核電站同诫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏樟澜。R本人自食惡果不足惜误窖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秩贰。 院中可真熱鬧霹俺,春花似錦、人聲如沸毒费。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)觅玻。三九已至想际,卻和暖如春培漏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胡本。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工牌柄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侧甫。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓珊佣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親披粟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咒锻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

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

  • 基本知識(shí) 其實(shí), 接觸了這么久的 AOP, 我感覺, AOP 給人難以理解的一個(gè)關(guān)鍵點(diǎn)是它的概念比較多, 而且坑爹...
    永順閱讀 8,236評(píng)論 5 114
  • 因?yàn)楣ぷ餍枨螅约喝チ私庖幌耡op并做下的記錄僻爽,當(dāng)然大部分都是參考他人博客以及官方文檔虫碉。 目錄 [關(guān)于 AOP](...
    forip閱讀 2,278評(píng)論 1 20
  • What? As we all know贾惦,在進(jìn)行項(xiàng)目構(gòu)建時(shí)胸梆,追求各模塊高內(nèi)聚,模塊間低耦合须板。然而現(xiàn)實(shí)并不總是如此美...
    MasterNeo閱讀 2,083評(píng)論 0 17
  • 這部分的參考文檔涉及數(shù)據(jù)訪問和數(shù)據(jù)訪問層和業(yè)務(wù)或服務(wù)層之間的交互碰镜。 Spring的綜合事務(wù)管理支持覆蓋很多細(xì)節(jié),然...
    竹天亮閱讀 1,038評(píng)論 0 0
  • 這是我們關(guān)于商業(yè)趨勢(shì)的No.006篇文章柠横。 在上一篇《移動(dòng)互聯(lián)網(wǎng)到底改變了什么?》的結(jié)尾课兄,我們聊到移動(dòng)互聯(lián)網(wǎng)改變了...
    老虎老濕閱讀 325評(píng)論 0 5