AOP埋點從入門到放棄(二)

筆墨Android發表於2018-08-12

其實人最大悲哀莫過於知道自己想要什麼,卻不知道怎麼堅持!最近迷戀上了死侍 其實和我平時的狀態差不多,以一個混子的心態去做任何事情,往往成功的概率會更大!!!

一張圖片鎮樓!!!

AOP埋點從入門到放棄(二)

上文說到了AspectJ的整合問題,如果沒有看過上一篇文章的小夥伴可以看看本系列的第一篇文章。

AOP埋點從入門到放棄(一)

這篇文章充分的講解了關於AspectJ的整合問題,接下來我們講講怎麼更好的使用AspectJ來唯我所用。。。

1. 一些亂七八糟東西的解釋

其實我感覺這個東西說起來是最難的,因為要記住一大堆概念!其實記憶力是我的最煩的東西,但是我是一隻猿,一隻牛逼的猿!所以當背課文了...

先來看一段程式碼

@Aspect
public class TraceAspect {
    private static final String TAG = "hjl";

    @Before("execution(* android.app.Activity.on*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore: 切面的點執行了!" + key);
    }
}
複製程式碼

先說一下這段程式碼實現了什麼?主要實現了在Activity中所有以on開頭的方法前面列印一段**Log.e(TAG, "onActivityMethodBefore: 切面的點執行了!" + key);**程式碼!接下來我們來逐一講解!

1.1 頂部@Aspect的含義

關於@Aspect這個註解,是下面所有內容的基礎,如果沒有這個註解AspectJ沒有相應的入口,就不會有相應的切面了!AspectJ會找到所有@Aspect註解,然後

1.2 萬用字元的概念(Pointcut語法)(難點)

首先說一下萬用字元的大體格式:(我也不知道理解的對不對,但是專案中使用的時候沒有發現什麼不對的地方)

@註解 訪問許可權 返回值型別 類名 函式名 引數

大體上的萬用字元都是這個格式的,我們用上面的一個萬用字元去說明一下:

execution(* android.app.Activity.on*(..))
複製程式碼

execution 一般指定方法的執行,在往後因為沒有註解和訪問許可權的限制,所以這裡什麼也沒寫,返回值用*代替,說明可以用任何返回值,android.app.Activity.on*代表函式名稱的全路徑,後面的一個型號代表on後面可以接任何東西,後面的(..)代表其引數可以為任何值。

上面就是一段萬用字元的含義了!其實學習AspectJ的時候,我覺得最難懂的就是相應的操作符了,如果操作符弄明白了的話,真的就很簡單了!但是如果之前做過後臺的話,這個應該就很簡單了,就是Spring框架中的AOP是一樣的都是用的Pointcut語法。因為自己不是java後臺開發人員,所以解釋的可能不到位,你可以去找你們java後臺組的人去問問,學習一下!應該比我講的強很多,因為我真是第一次接觸這個東西!

因為平時沒接觸過,所以這裡就寫一些常用的吧!

分類

JPoint 說明 Pointcut語法說明
method execution 一般指定方法的執行 execution(MethodSignnature)
method call 函式被呼叫 call(MethodSignnature)
constructor call 建構函式被呼叫 call(ConstructorSignature)
constructor execution 建構函式執行內部 execution(ConstructorSignature)
field get 讀變數 get(FieldSIgnature)
field set 寫變數 set(FieldSIgnature)
handler 異常處理 handler(TypeSignature) 注意:只能和@Before()配合使用,不支援@After、@Around等
advice execution advice執行 adciceexectuin()

Signature參考

Sigbature 語法(間隔一個空格)
MethodSignature @註解 訪問許可權 返回值型別 類名.函式名(引數)
ConstructorSignature @註解 訪問許可權 類名.new(引數)
FieldSignature @註解 訪問許可權 變數型別 類名.類成員變數名

Signature語法明細

Sigbature語法明細 解釋
@註解 @完整類名,如果沒有則不寫
訪問許可權 public/private/portect,以及static/final,如果沒有則不寫 注意:如果只寫public則匹配出來的是全部,如果寫public final則匹配的是所有public final開頭的內容
返回值型別 如果不限定型別,使用萬用字元*表示
類名.函式名 可以使用的萬用字元,包括*和..以及+號。其中*號用於陪陪除.號之外的任意字元,而..則表示任意字package,+號表示子類 注意:1.ConstructorSignature的函式名只能為new 2.(.函式名可以不寫),重用和註解一起使用 3.不能以..開頭
變數型別 成員變數型別,*代表任意型別
類名.成員變數名 類名可以使用萬用字元,與函式。函式名類似

Advice內容

Advice 說明
@Before(Pointcut) 執行在jPoint之前
@After(Pointcut) 執行在jPoint之後
@Around(Pointcut) 替代原來的程式碼,如果要執行原來的程式碼,需要使用proceedingJoinPoint.proceed(); 注意:不可以和@After和@Before等一起使用

上面這寫表的你先簡單看一下,估計你一會還是會回來看的!!!

1.2.1 method->call的示例:

    @Pointcut("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
    public void callMethod() {
        //為了演示call方法的使用
    }

    @Before("callMethod()")
    public void beforeCallMethod(JoinPoint joinPoint) {
        Log.e(TAG, "call方法的演示");
    }
複製程式碼

說一下上面程式碼:@Pointcut是來註解方法的,call後面新增了一系列的萬用字元,簡單說就是一個方法的地址,*代表沒有返回值,@Before是說明在切片前執行!這裡千萬別把com.jinlong.aspectjdemo.MainActivity.callMethod這個地址寫錯了就行!

其實上面這段程式碼可以簡化為

    @Before("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
    public void beforeCallMethod(JoinPoint joinPoint){
        Log.e(TAG, "call方法的演示");
    }
複製程式碼

如果你把上面的@Before換為@After,那麼就會在方法之後列印!!!

再來看一段程式碼:

    @Around("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
    public void aroundCallMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        Log.e(TAG, "方法執行的時間為" + (endTime - startTime));
    }
複製程式碼

這段程式碼是統計方法執行時間的,這裡著重講兩個內容

  • joinPoint.proceed();還原你原來的程式碼,如果沒有這句,你原來的程式碼就沒了!沒了!
  • @Around 替代原來的程式碼,加上上面這句就可以還原了之前的程式碼了!

所以這裡就能計算出方法的執行時間了!!!也就是@Around的用法了!

1.2.2 method->execution 的示例:

之前的時候,我總覺得execution和call是一樣的,但是後來我知道了,他們最大的區別是這樣的!!!

比如你有一個方法:callMethod()對吧! 然後使用call是這個樣子滴~

call的相應方法();
callMethod();
複製程式碼

但是如果是execution的話就程式設計這個樣子滴了~

callMethod(){
   execution的相應方法(); 
}
複製程式碼

其他的就沒有什麼區別了,也就不在這裡舉例說明了。

1.2.3 構造方法的操作

先說下這個東西是構造方法上用的,也就是說針對於相應的構造方法進行相應切面操作的!但是我一隻有一個疑問不明白,如果我按照上面方法的萬用字元進行操作的話,按照常理說應該也是能在相應切面進行操作的才對啊!編譯不報錯,但就是怎麼也列印不出來結果,還請明白的大神幫我解答一下!

按照上面的表格還有一種方案解決相應構造方法的問題

    @Before("execution(com.jinlong.aspectjdemo.Person.new(..))")
    public void beforeConstructorExecution(JoinPoint joinPoint) {
        //這個是顯示Constructor的
        Log.e(TAG, "before->" + joinPoint.getThis().toString() + "#" + joinPoint.getSignature().getName());
    }
複製程式碼

這段程式碼我嘗試過了,可以列印出結果。也就是以**ConstructorSignature @註解 訪問許可權 類名.new(引數)**這種方式就可以列印出相應結果來!不過我勸你把那個@Before換成@After否則你列印出來的內容可能是一個空!

1.2.4 關於相應成員變數的問題

就是相當你可以修改類的成員變數,不管你怎麼設定最終返回的都是你設定的值!

看下面這段程式碼:

這裡是正常的一個類:

public class Person {
    private String name;
    private String age;

    public Person() {
    }

    public Person(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
複製程式碼

這才是核心程式碼!

    @Around("get(String com.jinlong.aspectjdemo.Person.age)")
    public String aroundFieldGet(ProceedingJoinPoint joinPoint) throws Throwable {
        // 執行原始碼
        Object obj = joinPoint.proceed();
        String age = obj.toString();
        Log.e(TAG, "age: " + age);
        return "100";
    }
複製程式碼

這裡用到的的是上面FieldSignature @註解 訪問許可權 變數型別 類名.類成員變數名上面這段程式碼的含義是這樣滴,在每次用到age這個內容的時候,都會被修改,但是有一個問題,就是你不能重寫類的**toString()**方法,如果你重寫了這個方法的話,obj返回的就是一個空!我還真不知道為什麼,還請明白的告知一二!,這裡面get是一個關鍵字,就這麼理解吧!因為沒有設定訪問許可權和註解,所以這裡直接就省返回的變數型別(String型別)和類成員變數名的全路徑了!

在看下面這段程式碼:

    @Around("set(String com.jinlong.aspectjdemo.Person.age)")
    public void aroundFieleSet(ProceedingJoinPoint joinPoint) {
        Log.e(TAG, "aroundFieleSet: " + joinPoint.getTarget().toString() +
                joinPoint.getSignature().getName());
    }
複製程式碼

這個可以對相應的age屬性進行設定的方法,也就是當發生賦值操作的時候都會被修改!這裡和大家說明一下,上面這段程式碼沒有**joinPoint.proceed();**程式碼,所以之前的程式碼中執行的內容就會失效了!也就是說被列印這段話替換了!其實上面這段程式碼你執行的時候你會發現一件事,Log被列印了兩次,為什麼呢?你想啊!this.age = age;在set方法中出現一次,而且還在構造方法中出現一次呢。仔細看看,所以這裡要排除構造方法總的那一次,怎麼處理呢?就要用到 withincode了!

1.2.5 withincode排除內容

怎麼理解這個東西呢?表示某個構造方法或函式中涉及到的JPoint。不理解吧!沒關係,看一段程式碼你就理解了!

    @Around("set(String com.jinlong.aspectjdemo.Person.age)&&!withincode(com.jinlong.aspectjdemo.Person.new(..))")
    public void aroundFieleSet(ProceedingJoinPoint joinPoint) throws Throwable {
        //設定相應的成員變數
        joinPoint.proceed();
    }
複製程式碼

在1.2.4上面說到set會在兩個地方都有,但是其實我是不想要構造方法中的那個的,怎麼把他排除呢?那就是後面新增的這句程式碼**com.jinlong.aspectjdemo.Person.new(..))**就是把構造方法中的內容排除!其實很好理解,就是排除相應的構造方法,可以簡單理解withincode就是帶著某個內容,但是由於取反了,所以就是不帶著這個東西了!!!就醬紫了。。。

1.2.5 handler的異常的捕捉

這個相比較之下就簡單一點了,直接上程式碼:

這是在程式碼中的一個異常,很簡單的一個異常,如果這個方法走了相應的catch,那麼就能捕獲相應的異常了!

    private void catchMethod() {
        try {
            int sum = 100 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
複製程式碼

關鍵程式碼來了。。。

    @Before("handler(java.lang.Exception)")
    public void handlerMethod() {
        Log.e(TAG, "handlerMethod: 異常產生了");
    }
複製程式碼

是不是很簡單,使用一個handler關鍵字,加上一個異常的全路徑,ok搞定,但是這裡一定要注意,前面的註解只能是@Before,切記!!!

1.2.6 關於註解的使用

有許多第三方你是不知道具體方法名稱的,但是你還想使用的話怎麼辦?那就是註解了,因為註解可以很好的解決這種需求。

再來看一段程式碼:

定義一段註解內容:

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}
複製程式碼

因為註解的這些內容不是本篇文章的重點,所以這裡不準備講解了。感興趣的你可以百度一下,這個註解主要是在編譯完成後也會起作用,並且是方法和成員變數都可以使用!

在加上下面這段程式碼就可以進行相應切面的操作了!

    @Pointcut("execution(@com.jinlong.aspectjdemo.DebugTrace * *(..))")
    public void DebugTraceMethod() {
    }

    @Before("DebugTraceMethod()")
    public void beforeDebugTraceMethod(JoinPoint joinPoint) {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "註解這個方法執行了: ");
    }
複製程式碼

看到在這段程式碼大家應該不怎麼陌生了,就是在方法內容新增相應的切面操作。最後在使用的方法的上面新增相應的註解就可以了!就這麼簡單!

    @DebugTrace
    private void mothod1() {
        Log.e(TAG, "方法1執行了");
    }
複製程式碼

上面基本上包含了我們APP使用中,能用到一些內容,如果有什麼講的不到位的地方還請指出。因為是第一次接觸這個東西,可能有些細節講解的不是很到位,還請諒解!!!

想看原始碼嗎?想看連結嗎?點這裡

相關文章