介紹
AspectJ是Java的一個簡單實用的面向方面的擴展。通過幾個新的構造,AspectJ提供了對一系列橫切關注的模塊化實現(xiàn)的支持。
在現(xiàn)有的Java開發(fā)項目中采用AspectJ可能是一個簡單而且增量的任務。一條路徑是從開發(fā)方面開始,繼續(xù)使用生產(chǎn)方面,然后在使用AspectJ建立經(jīng)驗之后再使用方面。采用也可以遵循其他途徑。例如,一些開發(fā)人員將從馬上使用生產(chǎn)方面受益。其他人可能幾乎可以立即編寫干凈的可重用方面。
AspectJ支持基于名稱和基于屬性的橫切。使用基于名稱的橫切的方面傾向于影響少數(shù)其他類。但是,盡管規(guī)模較小,但與普通的Java實現(xiàn)相比,它們通常可以消除顯著的復雜性。使用基于屬性的橫切的方面可以具有小規(guī)模或大規(guī)模。
使用AspectJ會導致橫切關注的干凈模塊化的實現(xiàn)。當作為AspectJ方面編寫時,橫切關注的結構是明確的且易于理解的。方面也是高度模塊化的,使得開發(fā)橫切功能的即插即用實現(xiàn)成為可能。
基礎知識
Join point 連接點
Join Points | 定義 | 解釋 |
---|---|---|
method call | 調用方法 | 一般在執(zhí)行某個方法前會先調用該方法 |
method execution | 執(zhí)行方法 | 這個點是已經(jīng)執(zhí)行到了方法的內(nèi)部 |
constructor call | 調用構造方法 | 同調用方法 |
constructor execution | 執(zhí)行構造方法 | 同執(zhí)行方法 |
field get | 獲取參數(shù) | 比如獲取某個變量的值,get() |
field set | 設置參數(shù) | 比如設置某個變量的值,int num = 3 |
pre-initialization | 預初始化 | 在第一次初始化前會預初始化 |
initialization | 初始化 | 初始化類的時候會執(zhí)行 |
static initialization | 靜態(tài)初始化 | 靜態(tài)塊或靜態(tài)類初始化的時候會執(zhí)行 |
handler | 異常處理 | |
advice execution | 通知執(zhí)行 |
是指程序中可能作為代碼注入目標的特定的點,例如一個方法調用或者方法入口。
程序中連接點有很多,下面做一個表格一一指出:
Join Points | 定義 | 解釋 |
---|---|---|
method call | 調用方法 | 一般在執(zhí)行某個方法前會先調用該方法 |
method execution | 執(zhí)行方法 | 這個點是已經(jīng)執(zhí)行到了方法的內(nèi)部 |
constructor call | 調用構造方法 | 同調用方法 |
constructor execution | 執(zhí)行構造方法 | 同執(zhí)行方法 |
field get | 獲取參數(shù) | 比如獲取某個變量的值,get() |
field set | 設置參數(shù) | 比如設置某個變量的值,int num = 3 |
pre-initialization | 預初始化 | 在第一次初始化前會預初始化 |
initialization | 初始化 | 初始化類的時候會執(zhí)行 |
static initialization | 靜態(tài)初始化 | 靜態(tài)塊或靜態(tài)類初始化的時候會執(zhí)行 |
handler | 異常處理 | |
advice execution | 通知執(zhí)行 |
//這里可以用一個例子來演示一下所有的連接點
Pointcut 切入點
一個程序中會有很多JPoint連接點,但不一定我們都要去關注。那么我們可以選擇我們需要的點來作為切入點。
我們利用Pointcut的功能來篩選出對我們有用的點作為切入,pointcut有一套專門的語法,只要搞懂他后面就不愁了。
一個例子
@Pointcut("within(@com.jie.aoptest.aop.DebugLog *)")
public void withinAnnotatedClass() {}
@Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
public void methodInsideAnnotatedType() {}
@Pointcut("execution(@com.jie.aoptest.aop.DebugLog * *(..)) || methodInsideAnnotatedType()")
public void method() {}
這個例子表明切入點在DebugLog類中所有執(zhí)行方法的點,這里用到了within和execution兩個指示符,within用于匹配指定類型內(nèi)的方法執(zhí)行,而exection則用于匹配方法執(zhí)行的連接點。有synthetic標記的field和method是class內(nèi)部使用的,正常的源代碼里不會出現(xiàn)synthetic field。
切入點指示符
指示符 | 說明 |
---|---|
execution | 用于匹配方法執(zhí)行的連接點 |
within | 用于匹配指定類型內(nèi)的方法執(zhí)行 |
this: | 用于匹配當前AOP代理對象類型的執(zhí)行方法;注意是AOP代理對象的類型匹配,這樣就可能包括引入接口也類型匹配 |
target | 用于匹配當前目標對象類型的執(zhí)行方法;注意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配 |
args | 用于匹配當前執(zhí)行的方法傳入的參數(shù)為指定類型的執(zhí)行方法 |
@within | 用于匹配所以持有指定注解類型內(nèi)的方法 |
@target | 用于匹配當前目標對象類型的執(zhí)行方法,其中目標對象持有指定的注解 |
@args | 用于匹配當前執(zhí)行的方法傳入的參數(shù)持有指定注解的執(zhí)行 |
@annotation | 用于匹配當前執(zhí)行方法持有指定注解的方法 |
常用指示符
指示符 | 說明 |
---|---|
execution | 用于匹配方法執(zhí)行的連接點 |
within | 用于匹配指定類型內(nèi)的方法執(zhí)行 |
this: | 用于匹配當前AOP代理對象類型的執(zhí)行方法;注意是AOP代理對象的類型匹配,這樣就可能包括引入接口也類型匹配 |
target | 用于匹配當前目標對象類型的執(zhí)行方法;注意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配 |
args | 用于匹配當前執(zhí)行的方法傳入的參數(shù)為指定類型的執(zhí)行方法 |
@within | 用于匹配所以持有指定注解類型內(nèi)的方法 |
@target | 用于匹配當前目標對象類型的執(zhí)行方法,其中目標對象持有指定的注解 |
@args | 用于匹配當前執(zhí)行的方法傳入的參數(shù)持有指定注解的執(zhí)行 |
@annotation | 用于匹配當前執(zhí)行方法持有指定注解的方法 |
AspectJ切入點支持的切入點指示符還有: call、get、set、preinitialization、staticinitialization、initialization、handler、adviceexecution、withincode、cflow、cflowbelow、if、@this、@withincode,感興趣的可以了解,就不一一說明了。
類型匹配語法
先來看一下AspectJ類型匹配的通配符
- *:匹配任何數(shù)量字符;
- ..:匹配任何數(shù)量字符的重復,如在類型模式中匹配任何數(shù)量子包;而在方法參數(shù)模式中匹配任何數(shù)量參數(shù)。
- +:匹配指定類型的子類型;僅能作為后綴放在類型模式后邊。
接下來看一下具體匹配表達式類型
- 注解:可選,方法上持有的注解,如@Deprecated;
- 修飾符:可選,如public、protected;
- 返回值類型:必填,可以是任何類型模式;“*”表示所有類型;
- 類型聲明:可選,可以是任何類型模式;
- 方法名:必填,可以使用“*”進行模式匹配;
- 參數(shù)列表:“()”表示方法沒有任何參數(shù);“(..)”表示匹配接受任意個參數(shù)的方法,“(..,java.lang.String)”表示匹配接受java.lang.String類型的參數(shù)結束,且其前邊可以接受有任意個參數(shù)的方法;“(java.lang.String,..)” 表示匹配接受java.lang.String類型的參數(shù)開始,且其后邊可以接受任意個參數(shù)的方法;“(*,java.lang.String)” 表示匹配接受java.lang.String類型的參數(shù)結束,且其前邊接受有一個任意類型參數(shù)的方法;
- 異常列表:可選,以“throws 異常全限定名列表”聲明,異常全限定名列表如有多個以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。
組合切入點表達式
AspectJ使用 且(&&)、或(||)、非(!)來組合切入點表達式。
常用場景舉例
Advice通知參數(shù)
前面已經(jīng)介紹了Join point 連接點和 Pointcut 切入點,如果基本掌握了的話那么恭喜你內(nèi)功已經(jīng)修煉7成了。
我們成功設置好切入點后需要獲取通知來執(zhí)行要切入的代碼片段,這里的通知相當于鉤子/回調方法,在程序執(zhí)行到JPoint時候會調起通知,接下來就介紹一下獲取通知的方式。
Advice通知有三種類型
類型 | 說明 |
---|---|
before() | 是指在JPoint之前可以執(zhí)行一些操作 |
after() | 是指在JPoint之后可以執(zhí)行一些操作 |
around() | 環(huán)繞JPoint執(zhí)行操作,它包含了前后兩個過程,使用這種類型需要手動調用procees方法來執(zhí)行原操作 |
來看一個例子
我們來用檢測是否登錄做一個例子
這個是檢查登錄的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface CheckLogin {
}
先用before和after兩個類型來做一個測試
@Pointcut("execution(@com.jie.aoptest.aop.CheckLogin * *(..))")
public void methodAnnotated() {
}
@Before("methodAnnotated()")
public void beforeMethod(ProceedingJoinPoint joinPoint) {
Log.d("aspect", "beforeMethod");
Log.d("login", "請您登錄");
Toast.makeText(App.getAppContext().getCurActivity(), "請您登錄", Toast.LENGTH_SHORT).show();
}
@After("methodAnnotated()")
public void afterMethod(ProceedingJoinPoint joinPoint) {
Log.d("aspect", "afterMethod");
}
來看一下打印日志是這樣的
11-12 06:30:52.476 10388-10388/com.jie.aoptest D/aspect: beforeMethod
11-12 06:30:52.477 10388-10388/com.jie.aoptest D/login: 請您登錄
11-12 06:30:52.486 10388-10388/com.jie.aoptest D/aspect: afterMethod
然后我們用around做一個測試
@Pointcut("execution(@com.jie.aoptest.aop.CheckLogin * *(..))")
public void methodAnnotated() {
}
@Around("methodAnnotated()")
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Log.d("aspect", "aroundMethod");
Log.d("login", "請您登錄");
Toast.makeText(App.getAppContext().getCurActivity(), "請您登錄", Toast.LENGTH_SHORT).show();
joinPoint.proceed();
Log.d("aspect", "aroundMethod");
}
打印日志是這樣的
11-12 06:35:09.696 10512-10512/com.jie.aoptest D/aspect: aroundMethod
11-12 06:35:09.696 10512-10512/com.jie.aoptest D/login: 請您登錄
11-12 06:35:09.700 10512-10512/com.jie.aoptest D/aspect: aroundMethod
兩種方式都可以,但要注意一點around和after兩種類型是有沖突的,around和before可以共存,所以還是建議兩種方式,一種before和after配合使用,一種around單獨使用。
參數(shù)的獲取
方法參數(shù)的獲取
方法參數(shù)的獲取很簡單,可以通過joinPoint.getArgs()來獲取參數(shù),舉個例子:
@Override
protected void onCreate(Bundle savedInstanceState) {
...
safe("haha", 20, true);
...
}
@Safe
private void safe(String a, int b, boolean c) {
Log.d("aop", "獲取參數(shù)")
}
通知方法
@Around("execution(!synthetic * *(..)) && methodAnnotated()")
public void aroundJoinPoint(final ProceedingJoinPoint joinPoint) throws Throwable {
for (Object arg : joinPoint.getArgs()) {
Log.d("arg", arg.toString());
}
joinPoint.proceed(joinPoint.getArgs());
}
日志打印
11-12 06:56:08.062 29915-29915/com.jie.aoptest D/arg: haha
11-12 06:56:08.062 29915-29915/com.jie.aoptest D/arg: 20
11-12 06:56:08.062 29915-29915/com.jie.aoptest D/arg: true
注解參數(shù)的獲取
直接上代碼例子
首先需要在注解上聲明參數(shù)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPermission {
//聲明參數(shù)
String declaredPermission();
}
然后看一下Activity中的調用方法,注意這里在注解后設置參數(shù)值
@CheckPermission(declaredPermission="android.permission.READ_PHONE_STATE")
private void checkPhoneState(){
Log.d("CheckPermission","Read Phone State succeed");
}
看一下切片類的寫法,注意這里在切點上要用@annotation來獲取注解對象,然后我們在aroundMethod方法中多了一個checkPermission對象,最后從這個對象中拿到注解參數(shù)
@Aspect
public class CheckPermissionAspect {
@Pointcut("execution(@com.jie.aoptest.aop.CheckPermission * *(..)) && @annotation(checkPermission)")
public void checkPermission(CheckPermission checkPermission){};
@Around("checkPermission(checkPermission)")
public void aroundMethod(JoinPoint joinPoint, CheckPermission checkPermission){
//從注解信息中獲取聲明的權限。
String neededPermission = checkPermission.declaredPermission();
Log.d("CheckPermissionAspect", joinPoint.toShortString());
Log.d("CheckPermissionAspect", "\tneeded permission is " + neededPermission);
}
}
最后來看一下輸出日志,證明我們已經(jīng)成功拿到注解參數(shù)了
11-12 08:00:21.203 24559-24559/com.jie.aoptest D/CheckPermissionAspect: execution(MainActivity.checkPhoneState())
11-12 08:00:21.203 24559-24559/com.jie.aoptest D/CheckPermissionAspect: needed permission is android.permission.READ_PHONE_STATE
11-12 08:00:21.203 24559-24559/com.jie.aoptest D/CheckPermission: Read Phone State succeed
總結
AspectJ解析基本就到這里了,掌握了它就可以全面的用AOP思想去解決問題了,核心還是在解決問題的思路。我也是在邊學習邊整理從而寫出這篇文檔,這里講解了一些AspectJ的基礎用法,高級用法大家可以從參考文獻的書中去慢慢探索。
參考文獻
- 深入理解Android之AOP 博主寫的細致入微,我也從中有所參考。
- Manning.AspectJ.in.Action第二版