來源:知乎 欲眼熊貓
面向切面編程(AOP是Aspect Oriented Program的首字母縮寫) 设褐,我們知道啄枕,面向對象的特點是繼承萌抵、多態(tài)和封裝稿饰。而封裝就要求將功能分散到不同的對象中去缰儿,這在軟件設計中往往稱為職責分配畦粮。實際上也就是說,讓不同的類設計不同的方法乖阵。這樣代碼就分散到一個個的類中去了宣赔。這樣做的好處是降低了代碼的復雜程度,使類可重用瞪浸。 但是人們也發(fā)現(xiàn)儒将,在分散代碼的同時,也增加了代碼的重復性对蒲。什么意思呢椅棺?比如說,我們在兩個類中齐蔽,可能都需要在每個方法中做日志两疚。按面向對象的設計方法,我們就必須在兩個類的方法中都加入日志的內容含滴。也許他們是完全相同的诱渤,但就是因為面向對象的設計讓類與類之間無法聯(lián)系,而不能將這些重復的代碼統(tǒng)一起來谈况。 也許有人會說勺美,那好辦啊,我們可以將這段代碼寫在一個獨立的類獨立的方法里碑韵,然后再在這兩個類中調用赡茸。但是,這樣一來祝闻,這兩個類跟我們上面提到的獨立的類就有耦合了占卧,它的改變會影響這兩個類。那么,有沒有什么辦法华蜒,能讓我們在需要的時候辙纬,隨意地加入代碼呢?這種在運行時叭喜,動態(tài)地將代碼切入到類的指定方法贺拣、指定位置上的編程思想就是面向切面的編程。 一般而言捂蕴,我們管切入到指定類指定方法的代碼片段稱為切面譬涡,而切入到哪些類、哪些方法則叫切入點啥辨。有了AOP昂儒,我們就可以把幾個類共有的代碼,抽取到一個切片中委可,等到需要時再切入對象中去,從而改變其原有的行為腊嗡。這樣看來着倾,AOP其實只是OOP的補充而已。OOP從橫向上區(qū)分出一個個的類來燕少,而AOP則從縱向上向對象中加入特定的代碼卡者。有了AOP,OOP變得立體了客们。如果加上時間維度崇决,AOP使OOP由原來的二維變?yōu)槿S了,由平面變成立體了底挫。從技術上來說恒傻,AOP基本上是通過代理機制實現(xiàn)的。 AOP在編程歷史上可以說是里程碑式的建邓,對OOP編程是一種十分有益的補充盈厘。
** 在運行時,動態(tài)地將代碼切入到類的指定方法官边、指定位置上的編程思想就是面向切面的編程沸手。**
場景:
分離復雜度,邏輯事務與非邏輯事務分離
具體場景有:
- 日志收集注簿;
- 權限驗證契吉;
- 數(shù)據(jù)驗證;
- 監(jiān)控诡渴;
-
更多
邏輯代碼與非邏輯代碼示例
基本需要了解的概念
Join Points
JPoints就是程序運行時的一些執(zhí)行點捐晶。那么,一個程序中,哪些執(zhí)行點是JPoints呢租悄?比如:
l 一個函數(shù)的調用可以是一個JPoint谨究。比如Log.e()這個函數(shù)。e的執(zhí)行可以是一個JPoint泣棋,而調用e的函數(shù)也可以認為是一個JPoint胶哲。
l 設置一個變量,或者讀取一個變量潭辈,也可以是一個JPoint鸯屿。比如Demo類中有一個debug的boolean變量。設置它的地方或者讀取它的地方都可以看做是JPoints把敢。
我們經臣陌冢看到的依賴注入經常是這樣用的:
@BindView(R.id.textView_commend_2)
CommonTabLayout tl_2;
@OnClick(R.id.textView_commend_2)
public void onClickAdd() {
//在該方法運行時 也是執(zhí)行點
}
上面代碼的作用相信大家都知道,那么在這里 設置變量tl_2
時是執(zhí)行點修赞,聲明方法onClickAdd
也是執(zhí)行點婶恼。
甚至 我做一個操作 ,比如點擊一個按鈕柏副,這個按鈕會執(zhí)行onClickAdd
方法勾邦,那么在我調用 onClickAdd
方法之前可以是執(zhí)行點、在執(zhí)行onCLickAdd
的過程中也可以是執(zhí)行點割择。
這段話說的有點繞口眷篇,如果沒有表達清楚,請見諒荔泳。
<center> AspectJ中的Join Point </center>
Join Points | 說明 | 示例 |
---|---|---|
method call | 函數(shù)調用 | 比如調用Log.e()蕉饼,這是一處JPoint |
method execution | 函數(shù)執(zhí)行 | 比如Log.e()的執(zhí)行內部,是一處JPoint玛歌。注意它和method call的區(qū)別昧港。method call是調用某個函數(shù)的地方。而execution是某個函數(shù)執(zhí)行的內部支子。 |
constructor call | 構造函數(shù)調用 | 和method call類似 |
constructor execution | 構造函數(shù)執(zhí)行 | 和method execution類似 |
field get | 獲取某個變量 | 比如讀取DemoActivity.debug成員 |
field set | 設置某個變量 | 比如設置DemoActivity.debug成員 |
pre-initialization | Object在構造函數(shù)中做得一些工作慨飘。 | |
initialization | Object在構造函數(shù)中做得工作 | |
static initialization | 類初始化 | 比如類的static{} |
handler | 異常處理 | 比如try catch(xxx)中,對應catch內的執(zhí)行 |
advice execution | 這個是AspectJ的內容 | 我自己也不太懂 |
Pointcuts
Pointcuts是什么呢译荞? Pointcuts的功能 是從眾多的 JoinPoint中找到指定的執(zhí)行點瓤的;
在上面的代碼中 我可以通過注解拿到 變量和方法;然而通過上面的解釋我們知道一個變量或者方法存在好多個執(zhí)行點吞歼,我方法在即將執(zhí)行之前是執(zhí)行點圈膏,執(zhí)行過程中也可以是個執(zhí)行點,set變量是執(zhí)行點篙骡,get變量也是執(zhí)行點稽坤,那么我拿到了變量或者方法丈甸,我在什么時候做事情呢? 我通過什么判斷呢尿褪? Pointcuts就是做這個事情的睦擂。
** 白話:pointCuts 的作用是找到指定的注解,通過注解拿到聲明的 變量或者方法杖玲,然后設置 變量/方法 在指定的執(zhí)行點 joinPoint 顿仇。**
然后我們就可以在 指定的執(zhí)行點下做我們自定義的一系列動作。
** Pointcuts的目標是提供一種方法使得開發(fā)者能夠選擇自己感興趣的JoinPoints摆马。**
pointcuts中最常用的選擇條件和Joinpoint的類型密切相關
題目: 小明的舊娃娃質量不好臼闻,沒用幾下折騰幾下就破掉了,小明很郁悶囤采,然后小明去買新娃娃述呐,這個店家給他推薦了一個蒼老師版本的娃娃,小明在付款的時候突然想到舊娃娃的質量不好蕉毯,提出要驗驗貨乓搬。‘
** 答案:**
@Pointcut("execution(@com.wenld.aspectjdemo.買娃娃 * *(..))")
public void executionAspectJ() {
驗貨...嘿嘿嘿
if(驗貨通過) 買
}
聲明我們要找的買娃娃
注解代虾,注解找到的方法 選擇蒼老師款+付款
(execution
)的時候 进肯,executionAspectJ
驗驗貨....
advice和aspect
恭喜,看到這個地方來褐着,AspectJ的核心部分就掌握一大部分了。現(xiàn)在托呕,我們知道如何通過pointcuts來選擇合適的JPoint含蓉。那么,下一步工作就很明確了项郊,選擇這些JPoint后馅扣,我們肯定是需要干一些事情的。比如前面例子中的 before 小明驗貨之類的着降。這其實JPoint在執(zhí)行前差油,執(zhí)行后,都執(zhí)行了一些我們設置的代碼蓄喇。
advice的類型
關鍵詞 | 說明 | 示例 |
---|---|---|
before() | before advice | 表示在JPoint執(zhí)行之前交掏,需要干的事情 |
after() | after advice | 表示JPoint自己執(zhí)行完了后妆偏,需要干的事情盅弛。 |
after():returning(返回值類型) after():throwing(異常類型) | returning和throwing后面都可以指定具體的類型钱骂,如果不指定的話則匹配的時候不限定類型 | 假設JPoint是一個函數(shù)調用的話叔锐,那么函數(shù)調用執(zhí)行完有兩種方式退出见秽,一個是正常的return,另外一個是拋異常解取。 注意步责,after()默認包括returning和throwing兩種情況 |
返回值類型 around() | before和around是指JPoint執(zhí)行前或執(zhí)行后備觸發(fā),而around就替代了原JPoint | around是替代了原JPoint肮蛹,如果要執(zhí)行原JPoint的話,需要調用proceed |
在Android 中AOP工具和庫
AspectJ: 一個 JavaTM 語言的面向切面編程的無縫擴展(適用Android)伦忠。
Javassist for Android: 用于字節(jié)碼操作的知名 java 類庫 Javassist 的 Android 平臺移植版。
DexMaker: Dalvik 虛擬機上气忠,在編譯期或者運行時生成代碼的 Java API赋咽。
ASMDEX: 一個類似 ASM 的字節(jié)碼操作庫,運行在Android平臺脓匿,操作Dex字節(jié)碼。
在Android 中AOP的使用
這里使用 AspectJ:
- 功能強大
- 支持編譯期和加載時代碼注入
- 易于使用
先要裝ASpectJ環(huán)境 項目中有安裝包米母,在下圖位置
安裝AspectJ命令 Java -jar Aspectj-xxx.jar
;
path 配置環(huán)境變量 c:\xxx\aspectjxx\bin
毡琉;
環(huán)境搭建好以后
其中還要在gradle內配置一些東西,具體詳細請看代碼慧耍,這里就不貼了丐谋。
注解類 AspectJAnnotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AspectJAnnotation {
String value();
}
執(zhí)行類AspectJTest
/**
* <p/>
* Author: 溫利東 on 2017/3/2 16:07.
* blog: http://blog.csdn.net/sinat_15877283
* github: https://github.com/LidongWen
*/
@Aspect
public class AspectJTest {
private static final String TAG = "tag00";
@Pointcut("execution(@com.wenld.aspectjdemo.AspectJAnnotation * *(..))")
public void executionAspectJ() {
}
@Around("executionAspectJ()")
public Object aroundAspectJ(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Log.i(TAG, "aroundAspectJ(ProceedingJoinPoint joinPoint)");
AspectJAnnotation aspectJAnnotation = methodSignature.getMethod().getAnnotation(AspectJAnnotation.class);
String permission = aspectJAnnotation.value();
if(permission.equals("權限A")) {
Object result=joinPoint.proceed();
Log.i(TAG, "有權限:"+permission);
return result;
}
return "";
}
}
MainActivity
@AspectJAnnotation(value = "權限A")
public String test() {
Log.i(TAG, "檢查權限");
return "test";
}
編譯完成后 會發(fā)現(xiàn)多了一個 MainActivity$AjcClosure1.class
類:
MainActivity.class
你會發(fā)現(xiàn) 注入了一些方法号俐;
示例源碼地址:https://github.com/LidongWen/AspectJDemo/tree/master
** 一些資料地址:**
https://en.wikipedia.org/wiki/AOP
l http://www.eclipse.org/aspectj/ <=AspectJ官方網站
l http://www.eclipse.org/aspectj/doc/released/runtime-api/index.html <=AspectJ類庫參考文檔,內容非常少
l http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/index.html <=@AspectJ文檔践美,以后我們用Annotation的方式最多洗贰。
AspectJ語法
http://www.eclipse.org/aspectj/doc/released/quick5.pdf 或者官方的另外一個文檔也可以:
http://www.eclipse.org/aspectj/doc/released/progguide/semantics.html