前言
本文是AOP入門(mén)系列的基礎(chǔ)文章蝠检,不會(huì)講太多很深入的東西,如果為你想學(xué)習(xí)AOP挚瘟,但是遇到了種種障礙而無(wú)法進(jìn)一步學(xué)習(xí)叹谁,那本文或許能給你提供一些幫助和思路。
我個(gè)人在學(xué)習(xí)aop的過(guò)程中也和很多新手一樣碰到過(guò)各種麻煩的問(wèn)題乘盖,比如說(shuō)在入門(mén)aop的時(shí)候焰檩,需要做aspectj
相關(guān)的配置,這一步失敗了就無(wú)法繼續(xù)coding
订框;比如析苫,切點(diǎn)表達(dá)式寫(xiě)錯(cuò)了,那就無(wú)法攔截原生方法做下一步的業(yè)務(wù)邏輯的操作布蔗;甚至說(shuō)對(duì)aop相關(guān)的很多概念模糊不清藤违,只知道幾個(gè)常用的相關(guān)注解@Aspect
、@Before
纵揍、@Around
,只能看著別人的demo去敲一下常用的案例卻無(wú)法在自己的項(xiàng)目中靈活的定制自己的aop议街。不過(guò)沒(méi)有關(guān)系泽谨,這個(gè)系列,我會(huì)從基礎(chǔ)---應(yīng)用---原理
幫大家了解aop相關(guān)的重要知識(shí)。
推薦閱讀系列文章
Android aop Advice(通知吧雹、增強(qiáng))
Android aop(AspectJ)查看新的代理類(lèi)
Android AOP面向切面編程詳解
防止按鈕連續(xù)點(diǎn)擊
下面開(kāi)始講解aop切點(diǎn)表達(dá)式骨杂,在這之前我希望你做好2件事:
- 1、aop是做什么的雄卷?能解決什么問(wèn)題搓蚪?為什么要用它?
- 2丁鹉、android studio中配置好aop妒潭。推薦使用aspectjx
一、案例分析:
@Aspect
public class Test {
final String TAG = Test.class.getSimpleName();
@Before("execution(* *..MainActivity.on*(..))")
public void logLifeCycle(JoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = joinPoint.getThis().getClass().getSimpleName();
Log.e(TAG, "class:" + className+" method:" + methodSignature.getName());
}
@Around("execution(* *..MainActivity.testAOP())")
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String key = proceedingJoinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodAroundFirst: before " + key+"do something");
//執(zhí)行原方法
proceedingJoinPoint.proceed();
Log.e(TAG, "onActivityMethodAroundSecond: after " + key+"do something");
}
}
這里寫(xiě)了2個(gè)方法揣钦,logLifeCycle()
是在MainActivity
的生命周期的方法中打印log
日志雳灾;onActivityMethodAround
是攔截MainActivity
中的一個(gè)testAOP()
方法,在原方法執(zhí)行執(zhí)行前冯凹、后做一些事情谎亩。
重點(diǎn)是搞清楚@Aspect
、@Before
宇姚、@Around
這幾個(gè)注解匈庭,難點(diǎn)是切點(diǎn)表達(dá)式的書(shū)寫(xiě)。"execution(* *..MainActivity.on*(..))"
和execution(* *..MainActivity.testAOP())
就是切點(diǎn)表達(dá)式浑劳。
這個(gè)地方是非常容易犯錯(cuò)的阱持,因?yàn)楹芏嗳藢?duì)這個(gè)規(guī)則沒(méi)有搞懂,對(duì)很多概念模糊不清呀洲,所以很多新手難以自定義切點(diǎn)表達(dá)式紊选。
下面從幾個(gè)基礎(chǔ)概念出發(fā),帶你逐步掌握自定義切點(diǎn)表達(dá)式道逗。
(提示:別看見(jiàn)概念有點(diǎn)多兵罢,分類(lèi)也多就害怕了,其實(shí)不用害怕的滓窍,這里先做一個(gè)全方位的了解卖词,后面寫(xiě)表達(dá)式的時(shí)候是很簡(jiǎn)單的)
二、AOP相關(guān)的幾個(gè)基礎(chǔ)概念
-
Joinpoint(連接點(diǎn))
:是指那些被攔截到的點(diǎn)吏夯,比如說(shuō)方法的調(diào)用此蜈,成員屬性的訪(fǎng)問(wèn)甚至異常的處理; -
Pointcut(切入點(diǎn))
:所謂切入點(diǎn)是指我們要對(duì)哪些 Joinpoint 進(jìn)行攔截的定義噪生。 -
Advice(通知)
:所謂通知是指攔截到 Joinpoint 之后所要做的事情就是通知裆赵。 -
Weaving(織入)
:是指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程. AspectJ 采用編譯期織入和類(lèi)裝在期織入 。 -
Aspect(切面)
:是切入點(diǎn)和通知(引介)的結(jié)合 跺嗽。
2.1Advice(通知)
類(lèi)型 | 描述 |
---|---|
Before | 前置通知, 在目標(biāo)執(zhí)行之前執(zhí)行通知 |
After | 后置通知, 目標(biāo)執(zhí)行后執(zhí)行通知 |
Around | 環(huán)繞通知, 在目標(biāo)執(zhí)行中執(zhí)行通知, 控制目標(biāo)執(zhí)行時(shí)機(jī) |
AfterReturning | 后置返回通知, 目標(biāo)返回時(shí)執(zhí)行通知 |
AfterThrowing | 異常通知, 目標(biāo)拋出異常時(shí)執(zhí)行通知 |
2.2切入點(diǎn)指示符
切入點(diǎn)指示符用來(lái)指示切入點(diǎn)表達(dá)式目的战授,AspectJ切入點(diǎn)指示符如下:
execution
:用于匹配方法執(zhí)行的連接點(diǎn)within
:限制鏈接點(diǎn)匹配指定的類(lèi)型this
:用于匹配當(dāng)前AOP代理對(duì)象類(lèi)型的執(zhí)行方法页藻;注意是AOP代理對(duì)象的類(lèi)型匹配,這樣就可能包括引入接口也類(lèi)型匹配target
:限制鏈接點(diǎn)匹配目標(biāo)對(duì)象為指定類(lèi)型的類(lèi)@within
:匹配所有使用了xx注解的類(lèi)(注意是類(lèi))@annotation
: 匹配使用了xx注解的方法(注意是方法)
2.3切入點(diǎn)表達(dá)式語(yǔ)法
比如上面案例中的execution
表達(dá)式
execution(* *..MainActivity.on*(..))
-
execution()
:切入點(diǎn)指示符植兰; -
第一個(gè)*
:表示任意方法返回類(lèi)型份帐; -
*..
:表示省略了MainActivity
的包名,當(dāng)然這里也可以寫(xiě)出完整包名楣导; -
on*
表示MainActivity
中所有以on
開(kāi)頭的方法废境; -
(..)
:表示方法的參數(shù)可以是任意類(lèi)型且個(gè)數(shù)任意
AspectJ類(lèi)型匹配的通配符:
1、 *:匹配任何數(shù)量字符筒繁;
2噩凹、..:匹配任何數(shù)量字符的重復(fù),如在類(lèi)型模式中匹配任何數(shù)量子包膝晾;而在方法參數(shù)模式中匹配任何數(shù)量參數(shù)栓始。
3、+:匹配指定類(lèi)型的子類(lèi)型血当;僅能作為后綴放在類(lèi)型模式后邊幻赚。
三、@Pointcut
3.1臊旭、@Pointcut的基本定義
這里還是以打印Activity生命周期為例落恼,先看看之前的寫(xiě)法
@Before("execution(* *..MainActivity.on*(..))")
public void logLifeCycle(JoinPoint joinPoint) { }
這里直接用通知@Before
+切入點(diǎn)表達(dá)式就完成了。
下面看看另一種實(shí)現(xiàn)方式
定義切點(diǎn)
//切點(diǎn)表達(dá)式
@Pointcut("execution(* *..MainActivity.on*(..))")
//切點(diǎn)簽名方法
private void log(){}
使用定義的Pointcut
//使用切點(diǎn)方法
@Before("log()")
public void logLifeCycle(JoinPoint joinPoint) { }
這2種方式的實(shí)現(xiàn)效果是完全一樣的离熏,但是方法2比方法1更靈活佳谦。@Pointcut是專(zhuān)門(mén)用來(lái)定義切點(diǎn)的,它讓切點(diǎn)表達(dá)式可以復(fù)用滋戳。
3.2钻蔑、@Pointcut帶參數(shù)
3.2切點(diǎn)指示符
切點(diǎn)指示符是切點(diǎn)定義的關(guān)鍵字,切點(diǎn)表達(dá)式以切點(diǎn)指示符開(kāi)始奸鸯∵湫Γ可以切點(diǎn)指示符來(lái)告訴切點(diǎn)將要匹配什么,有以下9種切點(diǎn)指示符:execution
娄涩、within
窗怒、this
、target
蓄拣、@within
扬虚、@annotation
,下面一一介結(jié)這幾種切點(diǎn)指示符球恤。
提示:由于篇幅有限辜昵,下面只展示切點(diǎn)表達(dá)式,完整代碼可以在文末地址下載查看
execution
execution
表達(dá)式語(yǔ)法:
execution(<修飾符模式>咽斧?<返回類(lèi)型模式><方法名模式>(<參數(shù)模式>)<異常模式>?)
注意:execution
的粒度為方法路鹰,也就是說(shuō)是匹配方法的
除了返回類(lèi)型模式贷洲、方法名模式和參數(shù)模式外收厨,其它項(xiàng)都是可選的晋柱。
execution
是一種使用頻率比較高的切點(diǎn)指示符,它以方法的執(zhí)行作為切入點(diǎn)诵叁。它可以說(shuō)是最重要的雁竞,其它的指示符都是輔助它的。
比如通過(guò)如下表達(dá)式可以精確地匹配到Person
類(lèi)里的eat()
方法
@Before("execution(* com.zx.aop1.Person.eat())")
如果要匹配Person
類(lèi)里的所有方法拧额,可以使用通配符碑诉。
@Before("execution(* com.zx.aop1.Person.*(..))")
第一個(gè)*
表示返回值為任意類(lèi)型,第二個(gè)*
表示這個(gè)類(lèi)里的所有方法侥锦,()
括號(hào)表示參數(shù)列表进栽,括號(hào)里的用兩個(gè)點(diǎn)號(hào)表示匹配任意個(gè)參數(shù),包括0個(gè)恭垦。
within
為了方便類(lèi)型(如接口快毛、類(lèi)名、包名)過(guò)濾方法番挺,AOP 提供了within
關(guān)鍵字唠帝。其語(yǔ)法格式如下:
within(<type name>)
注意:within
的粒度為類(lèi)
匹配com.zx.aop1.person.Student類(lèi)中的所有方法
@Pointcut("within(com.zx.aop1.person.Student)")
匹配com.zx.aop1.person包及其子包中所有類(lèi)中的所有方法
@Pointcut("within(com.zx.aop1.person..*)")
匹配com.zx.aop1.person.Person類(lèi)及其子類(lèi)的所有方法
@Pointcut("within(com.zx.aop1.person.Person+)")
匹配所有實(shí)現(xiàn)com.zx.aop1.person.Human接口的類(lèi)的所有方法,包括接口方法和實(shí)現(xiàn)類(lèi)的額外方法
@Pointcut("within(com.zx.aop1.person.Human+)")
args
用于匹配當(dāng)前執(zhí)行的方法傳入的參數(shù) (args屬于動(dòng)態(tài)切入點(diǎn)玄柏,這種切入點(diǎn)開(kāi)銷(xiāo)非常大襟衰,非特殊情況最好不要使用)
@Pointcut(value = "execution(* com.zx.aop1.MainActivity.testArgs(..)) && args(arg1)")
private void pc1(String arg1){}
/**
* 通過(guò)args()傳參數(shù)
* @param arg1
*/
@Before("pc1(arg1)")
public void testArgs1(String arg1){
Log.e(TAG, "testArgs1--- : "+arg1 );
}
這種獲取參數(shù)的方式不太靈活,而且開(kāi)銷(xiāo)大粪摘,所以可以用另外一種更加便捷的方式獲取參數(shù)瀑晒,可以通過(guò)joinPoint.getArgs()
的方式去拿方法參數(shù)。比如上面例子徘意,可以用如下方式實(shí)現(xiàn):
@Pointcut(value = "execution(* com.zx.aop1.MainActivity.testArgs(..))")
private void pc2(){}
/**
* 通過(guò)JoinPoint連接點(diǎn)的方式去拿方法參數(shù)
* @param joinPoint
*/
@Before("pc2()")
public void testArgs2(JoinPoint joinPoint){
for (Object arg : joinPoint.getArgs()) {
Log.e(TAG, "testArgs2---: "+arg );
}
}
@within
語(yǔ)法:
@within(注解類(lèi)型)
匹配所有使用了CheckWithin
注解的類(lèi)(注意是類(lèi))
@Pointcut("@within(com.zx.aop1.CheckWithin)")
只能作用于類(lèi)苔悦,不能是方法,也不能是接口映砖。
@annotation
匹配使用了CheckAop
注解的方法(注意是方法)
@Pointcut("@annotation(com.zx.aop1.CheckAop)")
private void aAnnotation1() {
}
@After("aAnnotation1()")
public void testaAnnotation(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CheckAop checkAop = method.getAnnotation(CheckAop.class);
Log.e(TAG, "testaAnnotation--: "+checkAop.value() );
}
這里通過(guò)JoinPoint
拿到對(duì)應(yīng)的方法间坐,再通過(guò)反射獲取該方法的注解的值。此外還有一種簡(jiǎn)單的方法來(lái)獲取注解值邑退。
@Pointcut(value = "@annotation(checkAop)")
private void aAnnotation2(CheckAop checkAop) {
}
@After("aAnnotation2(checkAop)")
public void testaAnnotation2(CheckAop checkAop) {
Log.e(TAG, "testaAnnotation2---: "+checkAop.value() );
}
target
用來(lái)匹配的鏈接點(diǎn)所屬目標(biāo)對(duì)象必須是指定類(lèi)型的實(shí)例竹宋。
public interface Human {
void setGender(int gender);
}
定義接口實(shí)現(xiàn)方法
public class Person implements Human {
public int gender;
@Override
public void setGender(int gender) {
this.gender = gender;
}
}
定義調(diào)用代碼
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Person person = new Person();
person.setGender(1);
}
}
Aspect定義1:target() && call()
@Before("target(com.zx.aop1.person.Human) && call(* *..setGender(..))")
public void testThis(JoinPoint joinPoint){
Log.e(TAG, "testThis ----: "+joinPoint.toString() );
Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
}
Aspect定義2:target() && execution()
@Before("target(com.zx.aop1.person.Human) && execution(* *..setGender(..))")
public void testThis(JoinPoint joinPoint){
Log.e(TAG, "testThis ----: "+joinPoint.toString() );
Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
}
結(jié)果分析:當(dāng)使用
target()
時(shí),不管用execution()
還是call()
,Target對(duì)象都是Person
,而this對(duì)象是不一樣的地技。
this
用來(lái)匹配鏈接點(diǎn)所屬的對(duì)象引用是某個(gè)特定類(lèi)型的實(shí)例蜈七。
Aspect定義3:target() && call()
@Before("this(com.zx.aop1.person.Human) && call(* *..setGender(..))")
public void testThis(JoinPoint joinPoint){
Log.e(TAG, "testThis ----: "+joinPoint.toString() );
Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
}
結(jié)果:這里是沒(méi)有任何日志輸出的,因?yàn)檫@里是沒(méi)有插入代碼的莫矗。
原因待會(huì)兒再說(shuō)飒硅。
Aspect定義4:this() && execution()
@Before("this(com.zx.aop1.person.Human) && execution(* *..setGender(..))")
public void testThis(JoinPoint joinPoint){
Log.e(TAG, "testThis ----: "+joinPoint.toString() );
Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
}
結(jié)果分析:當(dāng)使用
this()
時(shí)砂缩,如果用call()
,this對(duì)象是空三娩,如果用execution()
還是,this對(duì)象都是Person
庵芭。原因也很簡(jiǎn)單,本案例的this對(duì)象只能是Human
接口的實(shí)現(xiàn)類(lèi)雀监,而當(dāng)你用用call()
時(shí)双吆,插入代碼是在MainActivity
中。
target
與this
總結(jié):
1会前、target指代的是切點(diǎn)方法的所有者好乐,而this指代的是被織入代碼所屬類(lèi)的實(shí)例對(duì)象。
2瓦宜、如果當(dāng)前要代理的類(lèi)沒(méi)有實(shí)現(xiàn)某個(gè)接口就用this
蔚万;如果實(shí)現(xiàn)了某個(gè)接口,就使用target
临庇。
組合切點(diǎn)表達(dá)式
&&
:要求連接點(diǎn)同時(shí)匹配兩個(gè)切點(diǎn)表達(dá)式||
:要求連接點(diǎn)匹配至少一個(gè)切入點(diǎn)表達(dá)式!
:要求連接點(diǎn)不匹配指定的切入點(diǎn)表達(dá)式
匹配所有實(shí)現(xiàn)Human
接口的類(lèi)的所有方法且方法的第一個(gè)參數(shù)為int
類(lèi)型
@Pointcut("within(com.zx.aop1.person.Human+) && execution(* com.zx.aop1.person...* *(int,..))")
更多切點(diǎn)指示符和切點(diǎn)表達(dá)式的應(yīng)用反璃,請(qǐng)關(guān)注后續(xù)文章!
寫(xiě)文不易苔巨,如果本文對(duì)你有所幫助版扩,請(qǐng)點(diǎn)個(gè)關(guān)注 或 贊!