AOP程式設計之AspectJ介紹及在Android中的應用

Sunmi_Android發表於2019-04-15

簡介

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;
    }
    }
複製程式碼

結果

png

無註解

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.*(..)

結果

png

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){}

    }
複製程式碼

結果

png

程式碼

github.com/xusoku/Aspe…

參考連結

AOP之AspectJ 技術原理詳解及實戰總結

相關文章