天天看點

非侵入式AOP監控之——AspectJ使用

一引言 二什麼是aspectj 2.1 它隻是一個代碼編譯器 2.2 它是用來做aop程式設計的 2.3為什麼要用aspectj 三aspectj原理與運用 3.1 基本原理 3.2 使用方式 3.2.1 純注解方式 3.2.2 aspectj語言 3.2.3 結合自定義注解使用 四 使用aspectj進行監聽方法執行耗時 五一些比較常見的問題 六推薦文章 demo位址

本博文的目的不是詳細的介紹aspectj的細節這是我來實習做的第一個任務因為老大最近讓我用非侵入式的方法監測産品方法運作時間。查了很久有各種工具但都不好用也查到了dexposed但是它在5.0以後就不相容了是以沒用它。最後找到了aspectj學習之後成功監測了項目方法執行耗時并找出耗時方法。是以對其作了一些使用和重要概念上的總結。

如果在一個大型的項目當中使用手動修改源碼的方式來達到調試、監控的目的第一需要插入許多重複代碼列印日志監控方法執行時間代碼無法複用第二修改的成本太高處處需要手動修改分分鐘累死、眼花。

oop: 面向對象把所有的事物都當做對象看待是以每一個對象都有自己的生命周期都是一個封裝的整體。每一個對象都有自己的一套垂直的系列方法和屬性使得我們使用對象的時候不需要太多的關系它的内部細節和實作過程隻需要關注輸入和輸出這跟我們的思維方式非常相近極大的降低了我們的編寫代碼成本而不像c那樣讓人頭痛。但在現實世界中并不是所有問題都能完美得劃分到子產品中。舉個最簡單而又常見的例子現在想為每個子產品加上日志功能要求子產品運作時候能輸出日志。在不知道aop的情況下一般的處理都是先設計一個日志輸出子產品這個子產品提供日志輸出api比如android中的log類。然後其他子產品需要輸出日志的時候調用log類的幾個函數比如e(tag,...)w(tag,...)d(tag,...)i(tag,...)等。

aop: 面向對象程式設計固然是開啟另一個程式設計時代但是久而久之也顯露了它的缺點最明顯的一點就是它無法橫向切割某一類方法、屬性當我們需要了解某一類方法、某一類屬性的資訊時就必須要在每一個類的方法裡面即便他們是同樣的方法隻因是不同的類是以不同添加監控代碼在代碼量龐大的情況下這是一個不可取的方法。是以aop編産生了基于aop的程式設計可以讓我們橫向的切割某一類方法和屬性不需要關心他是什麼類别我覺得aop并不是與oop對立的而是為了彌補oop的不足因為有了aop我們的調試和監控就變得簡單清晰。

aspectj 意思就是java的aspectjava的aop。它其實不是一個新的語言它就是一個代碼編譯器ajc後面以此代替在java編譯器的基礎上增加了一些它自己的關鍵字識别和編譯方法。是以ajc也可以編譯java代碼。它在編譯期将開發者編寫的aspect程式編織到目标程式中對目标程式作了重構目的就是建立目标程式與aspect程式的連接配接耦合獲得對方的引用獲得的是聲明類型不是運作時類型和上下文資訊進而達到aop的目的這裡在編譯期還是修改了原來程式的代碼但是是ajc替我們做的。需要注意的就是aspectj是在編譯期重構的代碼是以獲得的對象是聲明類型如果要獲得運作時類型需要使用一些關鍵字this、target。

aspectj就是aop隻不過是面向java的。aop裡面有一些重要基本的概念

aspect切面實作了cross-cutting功能是針對切面的子產品。最常見的是logging子產品、方法執行耗時子產品這樣程式按功能被分為好幾層如果按傳統的繼承的話商業模型繼承日志子產品的話需要插入修改的地方太多而通過建立一個切面就可以使用aop來實作相同的功能了我們可以針對不同的需求做出不同的切面。

jointpoint連接配接點連接配接點是切面插入應用程式的地方該點能被方法調用而且也會被抛出意外。連接配接點是應用程式提供給切面插入的地方在插入地建立aspectj程式與源程式的連接配接。

下面清單上的是被aspectj認為是joinpoint的

非侵入式AOP監控之——AspectJ使用

advice處理邏輯 advice是我們切面功能的實作它是切點的真正執行的地方。比如像寫日志到一個檔案中advice包括before、after、around等在jointpoint處插入代碼到應用程式中。我們來看一看原aspectj程式和反編譯過後的程式。看完下面的圖我們就大概明白了aspectj是如何達到監控源程式的資訊了。

原activity代碼

非侵入式AOP監控之——AspectJ使用

---

非侵入式AOP監控之——AspectJ使用
非侵入式AOP監控之——AspectJ使用

pointcut切點 pointcut可以控制你把哪些advice應用于jointpoint上去通常你使用pointcuts通過正規表達式來把明顯的名字和模式進行比對應用。決定了那個jointpoint會獲得通知。分為call、execution、target、this、within等關鍵字具體含義見第四節

非侵入式AOP監控之——AspectJ使用

順便再附上一些切點比對規則

1比對方法

2比對構造函數

1、非侵入式監控 可以在不修監控目标的情況下監控其運作截獲某類方法甚至可以修改其參數和運作軌迹

2、學習成本低 它就是java隻要會java就可以用它。

3、功能強大可拓展性高 它就是一個編譯器+一個庫可以讓開發者最大限度的發揮實作形形色色的aop程式

先放一塊aspectj代碼這裡使用的都是aspectj較為常用的知識接着在解釋。

在編譯期對目标對象、方法做标記對目标類、方法進行重構将pointcut插入目标中截獲該目标的資訊以及上下文環境以達到非侵入代碼監控的目的——注意它隻能獲得對象的聲明如果對象的聲明式接口那麼預設情況下不使用this、target限制切點擷取的是聲明類型而不是具體運作時的類。

編寫aspect聲明aspect、pointcut和advise。

ajc編織 aspectj編譯器在編譯期間對所切點所在的目标類進行了重構在編譯層将aspectj程式與目标程式進行雙向關聯生成新的目标位元組碼即将aspectj的切點和其餘輔助的資訊類段插入目标方法和目标類中同時也傳回了目标類以及其執行個體引用。這樣便能夠在aspectj程式裡對目标程式進行監聽甚至操控。

execution 顧名思義它截獲的是方法真正執行的代碼區around方法塊就是專門為它存在的。調用around可以控制原方法的執行與否可以選擇執行也可以選擇替換。

4 . call 同樣從名字可以看出call截獲的是方法的調用區它并不截獲代碼真正的執行區域它截獲的是方法調用之前與調用之後與before、after配合使用在調用方法的前後插入joinpoint和before、after通知。它截獲的資訊并沒有execution那麼多它無法控制原來方法的執行與否隻是在方法調用前後插入切點是以它比較适合做一些輕量的監控方法調用耗時方法的傳回值等。

5 . around替代原理目标方法體被around方法替換原方法重新生成名為xxx_aroundbody(),如果要調用原方法需要在aspectj程式的around方法體内調用joinpoint.proceed()還原方法執行是這樣達到替換原方法的目的。達到這個目的需要雙方互相引用橋梁便是aspect類目标程式插入了aspect類所在的包擷取引用。aspectj通過在目标類裡面加入closure閉包類該類構造函數包含了目标類執行個體、目标方法參數、joinpoint對象等資訊同時該類作為切點原方法的執行代理該閉包通過aspect類調用around方法傳入aspect程式。這樣便達到了關聯的目的便可以在aspect程式中監控和修改目标程式。

6 . before與after before與after隻是在方法被調用前和調用之後添加joinpoint和通知方法直接插入原程式方法體中,調用aspectj程式定義的advise方法它并不替代原方法是在方法call之前和之後做一個插入操作。after分為returnning和throwing兩類前者是在正常returning之後調用後者是在throwing發生之後調用。預設的after是在finally處調用是以它包含了前面的兩種情況。

7 . 重要關鍵字

在其它關鍵字中必須要注意的就是this、target的使用和差別同時還有一個很重要的方法signature.getdeclaringtype();aspectj是在編譯期截獲的對象資訊是以它獲得的标簽隻是對象的聲明比如接口、抽象類而不是運作時具體的對象。如果想要獲得運作時對象就需要用this、target關鍵字

上面貼的代碼就是該方式也是最普遍的方式它不需要其他插件的支援eclipse中有ajdt可以支援aspectj關鍵字聲明但android studio中沒有改插件使用java的注解和ajc以及它的庫就可以完成aop程式設計非常友善而且可以在絕大部分支援java的ide中使用。缺點就是對于注釋部分的比對沒有檢錯功能。

在eclipse中使用ajdt插件,可以識别aspectj的文法。這樣編寫起來相對于注解要友善許多還提供檢錯功能比較強大。不過不是所有的ide都支援比如android studio目前就沒有我哭了好久。

這個是混合用法可以在execution、call中使用注解然後該注解标注在目标方法上就可以實作關聯并且截獲。這樣做的好處實在想不到最多就是可以精确定位到某一個方法那使用絕對路徑比對不也可以。而且還侵入了源碼。實在是不推薦不過我在網上看到有人這麼用了是以也貼上來了。如果哪位高手知道這樣做的精髓請一定指教。下面貼一下它的用法實作。

自定義注解及被标記的方法

切面

源程式代碼

<a href="https://github.com/david-kuper/aspectjdemo.git">aspectjdemo</a>

關鍵代碼

監聽方法執行時間

1問題aspectj中signature提供的getdeclaretype傳回的是聲明類型無法擷取運作時類型是以無法準确擷取接口運作時類别。

方案使用target關鍵字限制pointcut擷取目标對象通過反射擷取其運作時類别。

2問題使用target關鍵字限制pointcut擷取目标對象object之後無法擷取靜态方法不屬于對象

方案單獨将靜态方法提出來再與前面的target關鍵字限制的集合取并集。

3問題使用before、after通知測試方法耗時的精确度誤差較大

方案改用execution+around。兩點第一由于before、after是在原方法調用前後插入通知會影響本來所在方法快的執行速率第二同時before、after兩個操作無法保證是原子操作多線程情況下會有誤差。是以該用execution關鍵字截獲方法體的真正執行處使用around通知替代原方法原方法被更名但結構不變在around通知體内調用原方法計時這樣能夠真正還原方法執行耗時

<a href="http://blog.csdn.net/innost/article/details/49387395">深入了解android之aop</a>

<a href="https://eclipse.org/aspectj/doc/next/progguide/starting-development.html">官方英文文檔</a>

<a href="http://blog.csdn.net/zl3450341/article/details/7673979">跟我學習aspectj系列</a>

繼續閱讀