AOP編程之AspectJ介紹及在Android中的應用

簡介

AspectJ是一個面向切面的框架,它擴展了Java語言。AspectJ定義了AOP語法,它有一個專門的編譯器用來生成遵照Java字節編碼規範的Class文件.利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。java

主要功能

數據埋點,日誌記錄,性能統計,安全控制,事務處理,異常處理等等android

目的(爲何要用AspectJ)

將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,經過對這些行爲的分離,咱們但願能夠將它們獨立到非指導業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼。git

原理

AspectJ在代碼的編譯期間掃描目標程序,根據切點(PointCut)匹配,將開發者編寫的Aspect程序編織(Weave)到目標程序的.class文件中,對目標程序做了重構(重構單位是JoinPoint),目的就是創建目標程序與Aspect程序的鏈接(得到執行的對象、方法、參數等上下文信息),從而達到AOP的目的。github

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中的gradleapp

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

無註解

一、這個是掃描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

二、捕獲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 技術原理詳解及實戰總結

相關文章
相關標籤/搜索