1. AOP與OOP的區(qū)別
平時(shí)我接觸多的就是OOP(Object Oriented Programming面向?qū)ο螅┪北OP(Aspect Oriented Programming面向切面)這兩種編程方式,我用自己的語(yǔ)言來(lái)解釋一下這兩者的區(qū)別:
OOP:
專業(yè)術(shù)語(yǔ): OOP(面向?qū)ο缶幊蹋┽槍?duì)業(yè)務(wù)處理過(guò)程的實(shí)體及其屬性和行為進(jìn)行抽象封裝轻专,以獲得更加清晰高效的邏輯單元?jiǎng)澐帧?br> 面向?qū)ο髠?cè)重靜態(tài)棉安,名詞苏章,狀態(tài),組織顾复,數(shù)據(jù)班挖,載體是空間;
大白話: OOP面向?qū)ο蟮娜筇卣?: 封裝 , 繼承 , 多態(tài) 芯砸。這些特征也說(shuō)明了OOP是面向?qū)ο蟮南糗剑覀冏鍪裁炊际强紤]一個(gè)對(duì)象,我們需要完成一個(gè)任務(wù)的時(shí)候一般都想著把一些操作封裝成一個(gè)類假丧,所有的變量和操作都封裝到一個(gè)類里面双揪,那么這個(gè)類就是我們的對(duì)象,我們要實(shí)現(xiàn)某個(gè)特定的功能包帚,首先也想想著在這個(gè)對(duì)象里面去實(shí)現(xiàn)渔期。
面向?qū)ο笠彩怯忻黠@缺點(diǎn)的,比如我們想實(shí)現(xiàn)某些不是常用的功能渴邦,我們需要去在需要的對(duì)象中去一一實(shí)現(xiàn)這些功能疯趟,并我們要不斷去維護(hù)這些功能,一旦多了我們就會(huì)很累的谋梭。
比如Android中一些按鍵統(tǒng)計(jì)迅办、生命周期統(tǒng)計(jì),特定統(tǒng)計(jì)都是比較瑣碎的事情章蚣,要利用面向?qū)ο蟮乃枷肴?shí)現(xiàn)都不是很完美站欺,這就要求去一一實(shí)現(xiàn)姨夹,顯得很瑣碎。
AOP:
專業(yè)術(shù)語(yǔ): AOP則是針對(duì)業(yè)務(wù)處理過(guò)程中的切面進(jìn)行提取矾策,它所面對(duì)的是處理過(guò)程中的某個(gè)步驟或階段磷账,以獲得邏輯過(guò)程中各部分之間低耦合性的隔離效果。
大白話: AOP面向切面贾虽,我在使用的時(shí)候是關(guān)注具體的方法和功能切入點(diǎn)逃糟,不需要知道也不用關(guān)心所在什么類或者是什么對(duì)象,我們只關(guān)注功能的實(shí)現(xiàn)蓬豁,具體對(duì)象是誰(shuí)绰咽,愛(ài)誰(shuí)誰(shuí)!
網(wǎng)上很流行的說(shuō)明段子:
舉個(gè)簡(jiǎn)單的例子地粪,對(duì)于“雇員”這樣一個(gè)業(yè)務(wù)實(shí)體進(jìn)行封裝取募,自然是OOP/OOD的任務(wù),我們可以為其建立一個(gè)“Employee”類蟆技,并將“雇員”相關(guān)的屬性和行為封裝其中玩敏。而用AOP設(shè)計(jì)思想對(duì)“雇員”進(jìn)行封裝將無(wú)從談起。同樣质礼,對(duì)于“權(quán)限檢查”這一動(dòng)作片斷進(jìn)行劃分旺聚,則是AOP的目標(biāo)領(lǐng)域。而通過(guò)OOD/OOP對(duì)一個(gè)動(dòng)作進(jìn)行封裝眶蕉,則有點(diǎn)不倫不類砰粹。換而言之,OOD/OOP面向名詞領(lǐng)域造挽,AOP面向動(dòng)詞領(lǐng)域伸眶。
如果說(shuō)面向?qū)ο缶幊淌顷P(guān)注將需求功能劃分為不同的并且相對(duì)獨(dú)立,封裝良好的類刽宪,并讓它們有著屬于自己的行為,依靠繼承和多態(tài)等來(lái)定義彼此的關(guān)系的話界酒;那么面向方面編程則是希望能夠?qū)⑼ㄓ眯枨蠊δ軓牟幌嚓P(guān)的類當(dāng)中分離出來(lái)圣拄,能夠使得很多類共享一個(gè)行為,一旦發(fā)生變化毁欣,不必修改很多類庇谆,而只需要修改這個(gè)行為即可。
面向方面編程和面向?qū)ο缶幊滩坏皇腔ハ喔?jìng)爭(zhēng)的技術(shù)而且彼此還是很好的互補(bǔ)凭疮。面向?qū)ο缶幊讨饕糜跒橥粚?duì)象層次的公用行為建模饭耳。它的弱點(diǎn)是將公共行為應(yīng)用于多個(gè)無(wú)關(guān)對(duì)象模型之間。而這恰恰是面向方面編程適合的地方执解。有了 AOP寞肖,我們可以定義交叉的關(guān)系,并將這些關(guān)系應(yīng)用于跨模塊的、彼此不同的對(duì)象模型新蟆。AOP 同時(shí)還可以讓我們層次化功能性而不是嵌入功能性觅赊,從而使得代碼有更好的可讀性和易于維護(hù)。它會(huì)和面向?qū)ο缶幊毯献鞯煤芎谩?/p>
上面的段子不知道誰(shuí)說(shuō)的琼稻,很流行吮螺,但確實(shí)說(shuō)得很明白!
2. AOP的Java實(shí)現(xiàn)方式
上面說(shuō)過(guò)帕翻,AOP關(guān)注的方法功能點(diǎn)鸠补,事先不知道所在對(duì)象是誰(shuí),當(dāng)然程序的運(yùn)行都是需要拿到對(duì)象在運(yùn)行的嘀掸,要在知道方法功能點(diǎn)的前提下拿到對(duì)象并執(zhí)行紫岩,這就需要用到Java的動(dòng)態(tài)代理。
在java的動(dòng)態(tài)代理機(jī)制中横殴,有兩個(gè)重要的類或接口被因,一個(gè)是 InvocationHandler(Interface)逊朽、另一個(gè)則是 Proxy(Class)事富,我們可以自己寫代碼去定義自己的動(dòng)態(tài)代理,去實(shí)現(xiàn)AOP未舟,但是太麻煩了文狱。Java有很多AOP實(shí)現(xiàn)的庫(kù)粥鞋,JavaWeb里面有JBoss、SpringFramework瞄崇、AspectJ等等呻粹。Android我熟悉,Android中基于AOP實(shí)現(xiàn)的庫(kù)有ButterKnife苏研、dagger2等浊、EventBus3.0、Retrofit 2.0等等摹蘑,不是說(shuō)這些庫(kù)完全基于AOP實(shí)現(xiàn)筹燕,只是說(shuō)里面部分功能基于AOP。
3. Java Annotation
Java Annotation是JDK5.0引入的一種注釋機(jī)制衅鹿。Java源代碼里面有自己的注解撒踪,我們還是很常見(jiàn)的:
java中的Annotation:
@Deprecated -- @Deprecated 所標(biāo)注內(nèi)容,不再被建議使用大渤。
@Override -- @Override 只能標(biāo)注方法制妄,表示該方法覆蓋父類中的方法。
@Documented -- @Documented 所標(biāo)注內(nèi)容泵三,可以出現(xiàn)在javadoc中耕捞。
@Inherited -- @Inherited只能被用來(lái)標(biāo)注“Annotation類型”衔掸,它所標(biāo)注的Annotation具有繼承性。
@Retention -- @Retention只能被用來(lái)標(biāo)注“Annotation類型”砸脊,而且它被用來(lái)指定Annotation的RetentionPolicy屬性具篇。
@Target -- @Target只能被用來(lái)標(biāo)注“Annotation類型”,而且它被用來(lái)指定Annotation的ElementType屬性凌埂。
@SuppressWarnings -- @SuppressWarnings 所標(biāo)注內(nèi)容產(chǎn)生的警告驱显,編譯器會(huì)對(duì)這些警告保持靜默。
當(dāng)然我們也可以自定義注解瞳抓,只是我們自定的注解需要我們自己去反射或者動(dòng)態(tài)代理來(lái)處理注解一次來(lái)實(shí)現(xiàn)AOP埃疫,自己處理還是比較麻煩,反射也是比較耗費(fèi)性能的孩哑,不建議使用栓霜,最好是Annotation+APT來(lái)做,把注解翻譯成Java代碼横蜒,避免性能損耗胳蛮,APT后面會(huì)說(shuō)到。
Java Annotation的本質(zhì)是使用了java的動(dòng)態(tài)代理丛晌,Annotation并不代表AOP仅炊,Java的AOP實(shí)現(xiàn)可以基于Java動(dòng)態(tài)代理,也可以基于Annotation澎蛛,但是自定義的Annotation需要結(jié)合 Java的動(dòng)態(tài)代理才可以實(shí)現(xiàn)AOP抚垄。
雖然源代碼我沒(méi)看過(guò),JavaWeb的一些庫(kù)JBoss谋逻、SpringFramework呆馁、AspectJ等等也是有使用Java Annotation,不可能所有的AOP都是完全自己去用動(dòng)態(tài)代理實(shí)現(xiàn)毁兆,不然寫起來(lái)也費(fèi)勁浙滤,用起來(lái)也不方便。
Java的Class類中有一系列支持Annotation和反射的實(shí)現(xiàn):
[點(diǎn)擊查看大圖]
里面有一個(gè)很關(guān)鍵的接口:AnnotatedElement气堕,里面是Class反射時(shí)對(duì)Annotation支持的一系列方法:
[點(diǎn)擊查看大圖]
具體的功能我就不解釋了纺腊,自己去查一下。
基于java的Annotation中的Target和Retention結(jié)合類似反射原理我們可以實(shí)現(xiàn)自己的AOP送巡。很多Android的AOP實(shí)現(xiàn)也是這么玩的。
4. Annotation的Android實(shí)現(xiàn)方式
Android是用Java寫的盒卸,上面說(shuō)到了的上面Java Annotation 動(dòng)態(tài)代理當(dāng)然都適用于Android骗爆,但是Android也有自己的AOP實(shí)現(xiàn)方式,但是Android的AOP實(shí)現(xiàn)原理跟Java的實(shí)現(xiàn)原理是一樣的蔽介。
再次強(qiáng)調(diào):
Java Annotation的本質(zhì)是使用了java的動(dòng)態(tài)代理摘投,Annotation并不代表AOP煮寡,Java的AOP實(shí)現(xiàn)可以基于Java動(dòng)態(tài)代理,也可以基于Annotation犀呼,但是自定義的Annotation需要結(jié)合 Java的動(dòng)態(tài)代理才可以實(shí)現(xiàn)AOP幸撕。
4.1 Android自帶基于Annotation的AOP實(shí)現(xiàn)
Android源碼中也有Google官方自定義的AndroidAnnotations,下面是代碼結(jié)構(gòu):
[點(diǎn)擊查看大圖]
右邊代碼展示的是Android自帶的Keep注解外臂,就是防止代碼混淆用到的坐儿,可以看到Keep同樣是基于Java Annotation自定義的而來(lái)的。源代碼我沒(méi)去看宋光,Android自定的注解實(shí)現(xiàn)也是基于Annotation和APT(注解處理工具)的AOP操作貌矿。
4.2 自定義Annotation加反射實(shí)現(xiàn)findViewById
自己的Annotation:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject{
int value();
}
Annotation反射處理工具:
public class AnnotateUtils {
public static void injectViews(Activity activity) {
Class<? extends Activity> object = activity.getClass(); // 獲取activity的Class
Field[] fields = object.getDeclaredFields(); // 通過(guò)Class獲取activity的所有字段
for (Field field : fields) { // 遍歷所有字段
// 獲取字段的注解,如果沒(méi)有ViewInject注解罪佳,則返回null
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int viewId = viewInject.value(); // 獲取字段注解的參數(shù)逛漫,這就是我們傳進(jìn)去控件Id
if (viewId != -1) {
try {
// 獲取類中的findViewById方法,參數(shù)為int
Method method = object.getMethod("findViewById", int.class);
// 執(zhí)行該方法赘艳,返回一個(gè)Object類型的View實(shí)例
Object resView = method.invoke(activity, viewId);
field.setAccessible(true);
// 把字段的值設(shè)置為該View的實(shí)例
field.set(activity, resView);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
}
使用示例:
@ViewInject(R.id.buy)
private Button buy;
@ViewInject(R.id.money)
private TextView money;
@ViewInject(R.id.tv_power)
private TextView power;
@ViewInject(R.id.tv_life)
private TextView life;
@ViewInject(R.id.tv_dex)
private TextView dex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AnnotateUtils.injectViews(this);
}
4.3 Android開源庫(kù)使用Java Annotation
下面是 butterknife使用的注解:
[點(diǎn)擊查看大圖]
左邊是butterknife的注解酌毡,右邊是最常用的View綁定注解BindView,可以看到也是使用了java的Annotation中的@Retention()和@Target()蕾管。
其他的庫(kù)其實(shí)也差不多枷踏,這里就不一一截圖驗(yàn)證了。
雖然Java Annotation并不是AOP娇掏,但是為了方便使用AOP呕寝,為了更好的使用我們往往需要使用Annotation來(lái)輔助。具體體現(xiàn)就是這些開源庫(kù)的各種注解婴梧。
黃金天然不是貨幣下梢,而貨幣天然是黃金,是不是可以這樣撕逼一下:Annotation天然不是AOP塞蹭,而AOP天然是Annotation孽江。
5. 大話AOP與Android的愛(ài)恨情仇之AOP三大精鋼
三大精鋼分別是:APT、AspectJ番电、Javassist
下面是這三個(gè)的作用時(shí)間:
5.1 APT
APT用來(lái)在編譯時(shí)期掃描處理源代碼中的注解信息岗屏,我們可以根據(jù)注解信息生成一些文件,比如Java文件漱办。利用APT為我們生成的Java代碼这刷,實(shí)現(xiàn)冗余的代碼功能,這樣就減少手動(dòng)的代碼輸入娩井,提升了編碼效率暇屋,而且使源代碼看起來(lái)更清晰簡(jiǎn)潔。
請(qǐng)參考:利用APT實(shí)現(xiàn)Android編譯時(shí)注解
Dagger洞辣、ButterKnife咐刨、AndroidAnnotation昙衅、EventBus的注解實(shí)現(xiàn)AOP為什么非要使用APT?
如果不使用用APT基于注解動(dòng)態(tài)生成java代碼定鸟,那么就需要在運(yùn)行的時(shí)候使用反射或者動(dòng)態(tài)代理而涉,那么耗費(fèi)了不必要的性能,本來(lái)就是為了方便的庫(kù)反而更加耗費(fèi)性能那就沒(méi)人用了联予,使用APT增加了代碼量啼县,但是不耗費(fèi)性能,用起來(lái)方便高性能無(wú)察覺(jué)躯泰。
可以說(shuō)基于Java Annotation的自定義注解配合APT實(shí)現(xiàn)了很多簡(jiǎn)單易用性能優(yōu)越的AOP用法的開源庫(kù)谭羔。
5.2 AspectJ
AspectJ是一個(gè)代碼生成工具(Code Generator)。
AspectJ語(yǔ)法就是用來(lái)定義代碼生成規(guī)則的語(yǔ)法麦向。
Aspectj一個(gè)易用的瘟裸、功能強(qiáng)大的aop編程語(yǔ)言,AspectJ是AOP的Java語(yǔ)言的實(shí)現(xiàn)诵竭,AspectJ是一個(gè)代碼生成工具(Code Generator)话告。
使用AspectJ有兩種方法:
- 完全使用AspectJ的語(yǔ)言。這語(yǔ)言一點(diǎn)也不難卵慰,和Java幾乎一樣沙郭,也能在AspectJ中調(diào)用Java的任何類庫(kù)。AspectJ只是多了一些關(guān)鍵詞罷了裳朋。
- 或者使用純Java語(yǔ)言開發(fā)病线,然后使用AspectJ注解,簡(jiǎn)稱@AspectJ鲤嫡。
使用AspectJ用java開發(fā)時(shí)使用的AspectJ注解其實(shí)也是基于Java Annotation的送挑,下面看結(jié)構(gòu)圖:
[點(diǎn)擊查看大圖]
左邊是所有的AspectJ注解,右邊是AspectJ注解中最關(guān)鍵的Aspect注解暖眼,可以看到也是使用了java的Annotation中的@Retention()和@Target()惕耕。
這是我之前做的基于AspectJ做的統(tǒng)計(jì),生命周期監(jiān)聽和click監(jiān)聽:
https://github.com/Dawish/CustomViews/tree/master/Aoplib
請(qǐng)參考牛逼的文章:http://blog.csdn.net/innost/article/details/49387395
關(guān)鍵詞 | 說(shuō)明 |
---|---|
Before | 切入點(diǎn)之前執(zhí)行诫肠,切入點(diǎn)執(zhí)行之前我們可以先執(zhí)行我們的方法司澎,可以攔截切入點(diǎn)的執(zhí)行,比如攔截需要用戶支付或者登陸的操作 |
after | 切入點(diǎn)之后執(zhí)行 栋豫,比如記錄用戶行為的操作 |
around | 包含切入點(diǎn)的前后挤安,切入點(diǎn)的執(zhí)行可以控制。比如需要條件判斷再執(zhí)行丧鸯,執(zhí)行完成后給用戶提示的操作 |
需要著重說(shuō)一下Advice蛤铜,Advice就是我們插入的代碼以何種方式插入,有Before還有After、Around昂羡。
關(guān)鍵詞 | 說(shuō)明 |
---|---|
Before | 切入點(diǎn)之前執(zhí)行,切入點(diǎn)執(zhí)行之前我們可以先執(zhí)行我們的方法摔踱,可以攔截切入點(diǎn)的執(zhí)行虐先,比如攔截需要用戶支付或者登陸的操作 |
after | 切入點(diǎn)之后執(zhí)行 ,比如記錄用戶行為的操作 |
around | 包含切入點(diǎn)的前后派敷,切入點(diǎn)的執(zhí)行可以控制蛹批。比如需要條件判斷再執(zhí)行,執(zhí)行完成后給用戶提示的操作 |
示例:
/**下面第一個(gè)*號(hào)表示被切入的方法不限定返回類型**/
@Before("execution(* android.view.View.OnClickListener.onClick(..))")
public void onUserAction(JoinPoint joinPoint){
Log.e(TAG, "aop statistics click");
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
View clickView = (View) args[0];
//你想做的操作
}
上面是在執(zhí)行onClick之前切入篮愉,我們可以根據(jù)JoinPoint 來(lái)獲取onClick(View v)方法本身的參數(shù)腐芍,來(lái)對(duì)點(diǎn)擊的View做操作,我們也可以根據(jù)用戶的連續(xù)點(diǎn)擊間隔時(shí)間來(lái)防止點(diǎn)擊速度過(guò)快導(dǎo)致的雙擊试躏。
是不是很強(qiáng)大猪勇。
我們可以用JoinPoint 參數(shù)來(lái)獲取更多的內(nèi)容:
- java.lang.Object[] getArgs():獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜恚?/li>
- Signature getSignature() :獲取連接點(diǎn)的方法簽名對(duì)象;
- java.lang.Object getTarget() :獲取連接點(diǎn)所在的目標(biāo)對(duì)象颠蕴;
- java.lang.Object getThis() :獲取代理對(duì)象本身泣刹;
有了Before、After犀被、Around和JoinPoint 的四者配合椅您,真是6得飛起,可上天入地啊寡键,可以很方便完成Android開發(fā)中很惡心很繁瑣的任務(wù)掀泳。
來(lái),我們走一個(gè)西轩!
5.3 Javassist
Javassist是一個(gè)開源的分析员舵、編輯和創(chuàng)建Java字節(jié)碼的類庫(kù)。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計(jì)算機(jī)科學(xué)系的 Shigeru Chiba
(千葉 滋)所創(chuàng)建的遭商。它已加入了開放源代碼JBoss
應(yīng)用服務(wù)器項(xiàng)目固灵,通過(guò)使用Javassist對(duì)字節(jié)碼操作為JBoss實(shí)現(xiàn)動(dòng)態(tài)"AOP"框架。 -- 百度百科
代表框架:熱修復(fù)框架HotFix 劫流、Savior(InstantRun)等
Javassist作用是在編譯器間修改class文件巫玻,與之相似的ASM(熱修復(fù)框架女媧)也有這個(gè)功能,可以讓我們直接修改編譯后的class二進(jìn)制代碼祠汇,首先我們得知道什么時(shí)候編譯完成仍秤,并且我們要趕在class文件被轉(zhuǎn)化為dex文件之前去修改。在Transfrom這個(gè)api出來(lái)之前可很,想要在項(xiàng)目被打包成dex之前對(duì)class進(jìn)行操作诗力,必須自定義一個(gè)Task,然后插入到predex或者dex之前我抠,在自定義的Task中可以使用javassist或者asm對(duì)class進(jìn)行操作苇本。而Transform則更為方便袜茧,Transfrom會(huì)有他自己的執(zhí)行時(shí)機(jī),不需要我們插入到某個(gè)Task前面瓣窄。Tranfrom一經(jīng)注冊(cè)便會(huì)自動(dòng)添加到Task執(zhí)行序列中笛厦,并且正好是項(xiàng)目被打包成dex之前。 來(lái)自--- http://www.reibang.com/p/dca3e2c8608a
Android熱補(bǔ)丁動(dòng)態(tài)修復(fù)技術(shù)請(qǐng)參考:http://blog.csdn.net/u010386612/article/details/51131642
Javassist我本身不是很熟俺夕,我就不多說(shuō)了裳凸,反正都TMD超級(jí)牛逼的東西。
本人家技術(shù)有限劝贸,上面很多都是自己基于自己的知識(shí)面說(shuō)的姨谷,不足不對(duì)之處還望各位同行不吝指教。