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

其實人最大悲哀莫過於知道本身想要什麼,殊不知道怎麼堅持!最近迷戀上了死侍 其實和我平時的狀態差很少,以一個混子的心態去作任何事情,每每成功的機率會更大!!!java

一張圖片鎮樓!!!android

上文說到了AspectJ的集成問題,若是沒有看過上一篇文章的小夥伴能夠看看本系列的第一篇文章。git

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

這篇文章充分的講解了關於AspectJ的集成問題,接下來咱們講講怎麼更好的使用AspectJ來惟我所用。。。編程

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

其實我感受這個東西提及來是最難的,由於要記住一大堆概念!其實記憶力是個人最煩的東西,可是我是一隻猿,一隻牛逼的猿!因此當背課文了...bash

先來看一段代碼app

@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語法)(難點)

首先說一下通配符的大致格式:(我也不知道理解的對不對,可是項目中使用的時候沒有發現什麼不對的地方)post

@註解 訪問權限 返回值類型 類名 函數名 參數

大致上的通配符都是這個格式的,咱們用上面的一個通配符去說明一下:

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使用中,能用到一些內容,若是有什麼講的不到位的地方還請指出。由於是第一次接觸這個東西,可能有些細節講解的不是很到位,還請諒解!!!

想看源碼嗎?想看連接嗎?點這裏

相關文章
相關標籤/搜索