什麼是AOP
以下是維基百科上對AOP的介紹:
面向切面的程式設計(Aspect-oriented programming,AOP,又譯作面向方面的程式設計、剖面導向程式設計)是計算機科學中的一種程式設計思想,旨在将橫切關注點與業務主體進行進一步分離,以提高程式代碼的子產品化程度。通過在現有代碼基礎上增加額外的通知(Advice)機制,能夠對被聲明為“切點(Pointcut)”的代碼塊進行統一管理與裝飾,如“對所有方法名以‘set*’開頭的方法添加背景日志”。該思想使得開發人員能夠将與代碼核心業務邏輯關系不那麼密切的功能(如日志功能)添加至程式中,同時又不降低業務代碼的可讀性。面向切面的程式設計思想也是面向切面軟體開發的基礎。
面向切面的程式設計将代碼邏輯切分為不同的子產品(即關注點(Concern),一段特定的邏輯功能)。幾乎所有的程式設計思想都涉及代碼功能的分類,将各個關注點封裝成獨立的抽象子產品(如函數、過程、子產品、類以及方法等),後者又可供進一步實作、封裝和重寫。部分關注點“橫切”程式代碼中的數個子產品,即在多個子產品中都有出現,它們即被稱作橫切關注點(Cross-cutting concerns, Horizontal concerns)。
什麼是AspectJ
以下是維基百科上對AspectJ的介紹:
AspectJ是在PARC為Java程式設計語言建立的面向方面的程式設計(AOP) 擴充。它在Eclipse Foundation開源項目中可用,既可獨立使用,也可內建到Eclipse 中。通過強調最終使用者的簡單性和可用性,AspectJ 已成為廣泛使用的 AOP 事實标準。自 2001 年首次公開釋出以來,它使用類似 Java 的文法,并包含用于顯示橫切結構的IDE 內建。
AspectJ在Android中的使用
本篇主要記錄的是AspectJ在Android中的使用,故不會記錄AspectJ的安裝以及使用AspectJ編譯aj檔案相關的知識點,如果需要了解這部分的知識,可以參考鄧凡平老師寫的深入了解Android之AOP。
下面舉個例子來說明AspectJ在Android中的使用:
比如我們希望在某個Activity的onCreate生命周期方法開始列印一行日志,當然你可以直接編碼來實作,但是如果使用AspectJ面向切面程式設計的方式,可以這麼做:
- 在Android項目根目錄的build.gradle檔案中加入如下代碼
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath 'org.aspectj:aspectjtools:1.9.7'
classpath 'org.aspectj:aspectjweaver:1.9.7'
}
- 在app/build.gradle檔案中加入如下代碼
android {
...
}
dependencies {
implementation 'org.aspectj:aspectjrt:1.9.7'
...
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
- 建立一個Java類并使用@Aspect注解,如下代碼
import android.util.Log;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MethodAspect {
private static final String TAG = "MethodAspect";
// *表示方法傳回值為任意值,..表示方法參數為任意值
@Pointcut("execution(* com.example.testaspectj.MainActivity.onCreate(..))")
public void jointPoint() {}
// 這裡的jointPoint()必須和上面的方法名對應
@Before("jointPoint()")
public void beforeOnCreate() {
Log.e(TAG, "this message is printed before onCreate...");
}
}
- 運作代碼啟動MainActivity,MainActivity不用做任何改動,直接用建立Android新項目時的代碼。程式運作後控制台會有如下列印: 在app/build/intermediates/javac/debug/classes/目錄下,你可以看到MainActivity.class檔案,通過AndroidStudio反編譯後源碼如下: 可以看到onCreate方法中插入了一行新代碼:
AspectJ知識點
AspectJ術語
- JointPoint:代碼可注入的點,比如一個方法的調用處或者方法内部、“讀、寫”變量等。
- Pointcut:用來描述 JPoint 注入點的一段表達式,比如:調用 Animal 類 fly 方法的地方,call(* Animal.fly(…))。
- Advice:常見的有 Before、After、Around 等,表示代碼執行前、執行後、替換目标代碼,也就是在 Pointcut 何處注入代碼。
- Aspect:Pointcut 和 Advice 合在一起稱作 Aspect。
AspectJ文法
Pointcut 中的 Signature 參考:
以上的 Signature 都是由一段表達式組成,且每個關鍵詞之間都有“空格”,下面是對關鍵詞的解釋:
Pointcut 文法熟悉了之後,Advice 就顯得很簡單了,它包含以下幾個:
AspectJ在Android中的應用還可以使用這個開源庫:https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx這個庫簡化了直接在Android項目中使用AspectJ的門檻,但是對于gradle插件的版本有要求(目前支援的gradle插件最高版本為3.6.1)
參考
- 深入了解Android之AOP
- AOP 之 AspectJ 全面剖析 in Android
- AspectJ在Android中的使用
源碼
本篇Demo的源碼可以在這裡下載下傳:https://github.com/yubo725/android-aspectj-demo/tree/v0.1