AspectJX基於AspectJ並在此基礎上擴展出來可應用於Android開發平臺的AOP框架,可作用於java源碼,class文件及jar包,同時支持kotlin的應用。
引入
- 插件引用
在項目根目錄的build.gradle裏依賴AspectJX
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
}
或者使用product目錄下的jar包,在你的項目根目錄下新建目錄plugins,把product/gradle-android-plugin-aspectjx-2.0.0.jar拷貝到plugins,依賴jar包
dependencies {
classpath fileTree(dir:'plugins', include:['*.jar'])
}
- 在app項目的build.gradle裏應用插件
apply plugin: 'android-aspectjx'
//或者這樣也可以
apply plugin: 'com.hujiang.android-aspectjx'
AOP可以做什麼
- 日誌
- 持久化
- 性能監控
- 數據校驗
- 緩存
AspectJ核心語法簡介
AspectJ 其實就是一種 AOP 框架,AOP 是實現程序功能統一維護的一種技術。利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合性降低,提高程序的可重用性,同時大大提高了開發效率。因此 AOP 的優勢可總結爲如下 兩點:
- 無侵入性。
- 修改方便。
下面介紹一下AspectJ的一些核心概念
- 橫切關注點:對哪些方法進行攔截,攔截後怎麼處理
- 切面(
Aspect
):對橫切關注點的抽象 - 連接點(
JoinPoint
):是程序的關鍵執行點,也是我們關注的點,它是指被攔截到的點(方法、字段、構造器等) - 切入點(
PointCut
):對JoinPoint
進行攔截的定義。PointCut的目的是提供一種方法使得開發者能夠選擇自己感興趣的JoinPoint
。 - 通知(
Advice
):切入點僅用於捕捉連接點集合,但是,除了捕捉連接點集合以外什麼事情都沒有做。事實上實現橫切行爲我們要使用通知。它 一般指攔截到JoinPoint
後要執行的代碼,分爲 前置、後置、環繞 三種類型。這裏,我們需要注意Advice Precedence
(優先權) 的情況,比如我們對同一個切面方法同時使用了@Before
和@Around
時就會報錯,此時會提示需要設置Advice
的優先級。
AspectJ
作爲一種基於 Java
語言實現的一套面向切面程序設計規範。它向 Java
中加入了 連接點(Join Point
) 這個新概念,其實它也只是現存的一個 Java 概念的名稱而已。它向 Java
語言中加入了少許新結構,譬如切入點(pointcut
)、通知(Advice
)、類型間聲明(Inter-type declaration
) 和切面(Aspect
)。切入點和通知動態地影響程序流程,類型間聲明則是靜態的影響程序的類等級結構,而切面則是對所有這些新結構的封裝。
對於AsepctJ
中的各個核心概念來說,其連接點就恰如程序流中適當的一點。而切入點收集特定的連接點集合和在這些點中的值。一個通知則是當一個連接點到達時執行的代碼,這些都是 AspectJ
的動態部分。其實連接點就好比是程序中那一條一條的語句,而切入點就是特定一條語句處設置的一個斷點,它收集了斷點處程序棧的信息,而通知就是在這個斷點前後想要加入的程序代碼。
此外,AspectJ
中也有許多不同種類的類型間聲明,這就允許程序員修改程序的靜態結構、名稱、類的成員以及類之間的關係。 AspectJ
中的切面是橫切關注點的模塊單元。它們的行爲與 Java
語言中的類很象,但是切面 還封裝了切入點、通知以及類型間聲明。
AspectJ的語法
- JoinPoint
一般定位在如下位置:
(1)函數調用
(2)獲取、設置變量
(3)類初始化
使用PointCut
對我們指定的連接點進行攔截,通過Advice
,就可以攔截到JoinPoint
後要執行的代碼。Advice
通常有以下 三種類型:
(1)Before
:PointCut
之前執行。
(2)After
:PointCut
之後執行。
(3)Around
:PointCut
之前、之後分別執行。
示例1,最簡單使用:
@Before("execution(* android.app.Activity.on**(..))")
public void onActivityCalled(JoinPoint joinPoint) throws Throwable {
Log.d(...)
}
其中,在 execution 中的是一個匹配規則,第一個 * 代表匹配任意的方法返回值,後面的語法代碼匹配所有 Activity 中以 on 開頭的方法。這樣,我們就可以 在 App 中所有 Activity 中以 on 開頭的方法中輸出一句log。
上面的 execution 就是處理 Join Point 的類型,通常有如下兩種類型:
(1)call:代表調用方法的位置,插入在函數體外面。
(2)execution:代表方法執行的位置,插入在函數體內部。
示例2,統計 Application 中的所有方法耗時:
@Aspect
public class ApplicationAop {
@Around("call (* com.json.chao.application.BaseApplication.**(..))")
public void getTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.i(TAG, name + " cost" + (System.currentTimeMillis() - time));
}
}
需要注意的是,當Advice爲Before或After時,函數參數爲JoinPoint,當爲Around時,函數參數爲ProceedingJoinPoint。而Around和Before及After最大的區別是,ProceedingJoinPoint不同於JoinPoint,其內部提供了proceed方法來執行目標函數。