簡介
AspectJ是一個面向切面的框架,它擴充套件了Java語言。AspectJ定義了AOP語法,它有一個專門的編譯器用來生成遵守Java位元組編碼規範的Class檔案.利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
主要功能
資料埋點,日誌記錄,效能統計,安全控制,事務處理,異常處理等等
目的(為什麼要用AspectJ)
將日誌記錄,效能統計,安全控制,事務處理,異常處理等程式碼從業務邏輯程式碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的程式碼。
原理
AspectJ在程式碼的編譯期間掃描目標程式,根據切點(PointCut)匹配,將開發者編寫的Aspect程式編織(Weave)到目標程式的.class檔案中,對目標程式作了重構(重構單位是JoinPoint),目的就是建立目標程式與Aspect程式的連線(獲得執行的物件、方法、引數等上下文資訊),從而達到AOP的目的。
Gradle 配置示例
要引入AspectJ到Android工程中,最重要的就是兩個包:
//在buildscript中新增該編織器,gradle構建時就會對class檔案進行編織 //在buildscript中新增該工具包,在構建工程的時候執行一些任務:打日誌等
classpath 'org.aspectj:aspectjtools:1.8.11'
classpath 'org.aspectj:aspectjweaver:1.8.9'
複製程式碼
//在dependencies中新增該依賴,提供@AspectJ語法
compile 'org.aspectj:aspectjrt:1.8.9'
複製程式碼
//在app中的gradle
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
複製程式碼
//列印gradle日誌
android.applicationVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(
File.pathSeparator)]
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
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:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
複製程式碼
AspectJ官網配置
fernandocejas.com/2014/08/03/…
語法
切面——Aspect
在AOP中切面是標記執行被切點標記方法的邏輯處理。就是切點要處理的具體邏輯在切面這個模組。
連線點——JoinPoint
它是切面插入執行應用程式的地方,他能被方法呼叫,也能新增方法,JoinPoint是一個執行鏈,每一個JoinPoint是一個單獨的閉包,在執行的時候將上下文環境賦予閉包執行方法體邏輯。
AspectJ的joinpoint有如下:
method call 函式呼叫
method execution 函式執行
constructor call 建構函式呼叫
constructor execution 建構函式執行
field get 獲取某個變數
field set 設定某個變數
pre-initialization Object在建構函式中做得一些工作。
initialization Object在建構函式中做得工作
static initialization 類初始化
handler 異常處理,比如try catch(xxx)中,對應catch內的執行
advice execution 裡面有3個標記(Around Before After)
複製程式碼
切點——PointCut
切點的宣告決定需要切割的JoinPoint的集合,Pointcut可以控制你把哪些advice應用於JoinPoint上去,通常通過正規表示式來進行匹配應用,決定了那個jointpoint會獲得通知。分為call、execution、target、this、within等關鍵字等。
within(TypePattem) TypePattern標示package或者類。TypePatter可以使用萬用字元
withincode(ConstructorSignaturelMethodSignature) 表示某個建構函式或其他函式執行過程中涉及到的JPoint
cflow(pointcuts) cflow是call flow的意思,cflow的條件是一個pointcut
cflowbelow(pointcuts) 表示呼叫pointcuts函式時所包含的JPoint,不包括pointcuts這個JPoint本身
this(Type) JPoint的this物件是Type型別
target(Type) JPoint的target物件是Type型別
args(TypeSignature) 用來對JPoint的引數進行條件搜尋的
複製程式碼
匹配規則
(1)型別匹配語法 型別匹配的萬用字元: *:匹配任何數量字元; ..:匹配任何數量字元的重複,如在型別模式中匹配任何數量子包;而在方法引數模式中匹配任何數量引數。 +:匹配指定型別的子型別;僅能作為字尾放在型別模式後邊。 AspectJ使用 且(&&)、或(||)、非(!)來組合切入點表示式。
(2)匹配模式 call(<註解?> <修飾符?> <返回值型別> <型別宣告?>.<方法名>(引數列表) <異常列表>?)
精確匹配
//表示匹配 com.sunmi.MainActivity類中所有被@Describe註解的public void方法。
@Pointcut("call(@Describe public void com.sunmi.MainActivity.init(Context))")
public void pointCut(){}
單一模糊匹配
//表示匹配 com.sunmi.MainActivity類中所有被@Describe註解的public void方法。
@Pointcut("call(@Describe public void com.sunmi.MainActivity.*(..)) ")
public void pointCut(){}
//表示匹配呼叫Toast及其子類呼叫的show方法,不論返回型別以及引數列表,並且該子類在以com.sunmi開頭的包名內
@Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.sunmi..*))")
public void toastShow() {
}
組合模糊匹配
//表示匹配任意Activity或者其子類的onStart方法執行,不論返回型別以及引數列表,且該類在com.sunmi包名內
@Pointcut("execution(* *..Activity+.onStart(..))&& within(com.sunmi.*)")
public void onStart(){}
複製程式碼
示例程式碼
有註解
宣告一個註解 AopPoint
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AopPoint {
String value();
int type() default 0;
}
複製程式碼
在Activity 中,定義了2個按鈕的點選事件,在方法上都標記註解,指定了value 和type。
@AopPoint(value = "首頁點選",type = 1)
public void doFunc1(View view) {
SystemClock.sleep(1000);
}
@AopPoint("分類點選")
public void doFunc2(View view) {
SystemClock.sleep(1000);
}
複製程式碼
編寫一個切面類
@Aspect
public class AopAspect {
@Pointcut("execution(@com.example.davis.aspectdemo.AopPoint * *(..))")
public void aopCut(){
}
@Around("aopCut()")
public Object dealMethod(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature= (MethodSignature) joinPoint.getSignature();
AopPoint aopPoint=signature.getMethod().getAnnotation(AopPoint.class);
String value=aopPoint.value();
int type=aopPoint.type();
TimeTool timeTool=new TimeTool();
timeTool.start();
Object result=joinPoint.proceed();
timeTool.stop();
Log.e("aopCut",value+" 執行時間="+timeTool.getTotalTimeMillis() +" type型別是"+type);
return result;
}
}
複製程式碼
結果
無註解
1、這個是掃描Activity 中onCreate方法的呼叫
@Aspect
public class FuncTAspect {
@Before("execution(* android.app.Activity.onCreate(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e("FuncTAspect", "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());
}
}
複製程式碼
結果只能掃描到onCreate方法
如果把onCreate改成萬用字元* android.app.Activity.onCreate(..) 改成android.app.Activity.*(..)
結果
2、捕獲catch異常
@Aspect
public class ExceptionHandleAspect {
private static final String TAG = "ExceptionHandleAspect";
/**
* 截獲空指標異常
*
* @param e
*/
@Pointcut("handler(java.lang.Exception)&&args(e)")
public void handle(Exception e) {
}
/**
* 在catch程式碼執行之前做一些處理
*
* @param joinPoint
* @param e 異常引數
*/
@Before(value = "handle(e)", argNames = "e")
public void handleBefore(JoinPoint joinPoint, Exception e) {
Log.e(TAG, joinPoint.getSignature().toLongString() + " handleBefore() :" + e.toString());
}
}
複製程式碼
在方法裡製造一個Null指標
public void doFunc1(View view) {
try {
AppItem appItem=null;
appItem.number=2;
}catch (Exception e){}
}
複製程式碼
結果