aop實(shí)現(xiàn)的三大方式(反射 (xutil) apt注解(ButterKnife) aspect (本文即將講到的)) 說出各自的優(yōu)缺點(diǎn)
一汹粤、AOP概念
百度百科中對(duì)AOP的解釋如下:
在軟件業(yè)田晚,AOP為Aspect Oriented Programming的縮寫贤徒,意為:面向切面編程汇四,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)踢涌。
AOP是OOP的延續(xù)睁壁,是軟件開發(fā)中的一個(gè)熱點(diǎn),也是很多框架如 java中的Spring框架中的一個(gè)重要內(nèi)容行剂,是函數(shù)式編程的一種衍生范型钳降。 利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低固阁,提高程序的可重用性备燃,同時(shí)提高了開發(fā)的效率凌唬。
AOP只是一種思想的統(tǒng)稱,實(shí)現(xiàn)這種思想的方法有挺多况褪。AOP通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)测垛。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離秧均,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性锯七,提高開發(fā)效率誉己。
(1)AOP與OOP的關(guān)系
OOP(面向?qū)ο缶幊蹋?/strong>針對(duì)業(yè)務(wù)處理過程的實(shí)體及其屬性和行為進(jìn)行抽象封裝,以獲得更加清晰高效的邏輯單元?jiǎng)澐衷牖5且灿兴娜秉c(diǎn)袱蜡,最明顯的就是關(guān)注點(diǎn)聚焦時(shí),面向?qū)ο鬅o法簡(jiǎn)單的解決這個(gè)問題,一個(gè)關(guān)注點(diǎn)是面向所有而不是單一的類迅细,不受類的邊界的約束淘邻,因此OOP無法將關(guān)注點(diǎn)聚焦來解決宾舅,只能分散到各個(gè)類中。
AOP(面向切面編程)則是針對(duì)業(yè)務(wù)處理過程中的切面進(jìn)行提取扶平,它所面對(duì)的是處理過程中的某個(gè)步驟或階段蔬蕊,以獲得邏輯過程中各部分之間低耦合性的隔離效果岸夯。這兩種設(shè)計(jì)思想在目標(biāo)上有著本質(zhì)的差異。
AOP并不是與OOP對(duì)立的勉吻,而是為了彌補(bǔ)OOP的不足旅赢。OOP解決了豎向的問題煮盼,AOP則解決橫向的問題。因?yàn)橛辛薃OP我們的調(diào)試和監(jiān)控就變得簡(jiǎn)單清晰踩娘。
簡(jiǎn)單的來講,AOP是一種:可以在不改變?cè)瓉泶a的基礎(chǔ)上雷绢,通過“動(dòng)態(tài)注入”代碼翘紊,來改變?cè)瓉韴?zhí)行結(jié)果的技術(shù)藐唠。
(2)AOP主要應(yīng)用場(chǎng)景
日志記錄,性能統(tǒng)計(jì)踪宠,安全控制柳琢,事務(wù)處理润脸,異常處理等等毙驯。
(3)主要目標(biāo)
將日志記錄,性能統(tǒng)計(jì)涩馆,安全控制允坚,事務(wù)處理稠项,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對(duì)這些行為的分離活逆,我們希望可以將它們獨(dú)立到非指導(dǎo)業(yè)務(wù)邏輯的方法中拗胜,進(jìn)而改變這些行為的時(shí)候不影響業(yè)務(wù)邏輯的代碼埂软。
[圖片上傳失敗...(image-f77692-1591768775090)]
上圖是一個(gè)APP模塊結(jié)構(gòu)示例,按照照OOP的思想劃分為“視圖交互”所灸,“業(yè)務(wù)邏輯”爬立,“網(wǎng)絡(luò)”等三個(gè)模塊,而現(xiàn)在假設(shè)想要對(duì)所有模塊的每個(gè)方法耗時(shí)(性能監(jiān)控模塊)進(jìn)行統(tǒng)計(jì)抡秆。這個(gè)性能監(jiān)控模塊的功能就是需要橫跨并嵌入眾多模塊里的吟策,這就是典型的AOP的應(yīng)用場(chǎng)景踊挠。
AOP的目標(biāo)是把這些橫跨并嵌入眾多模塊里的功能(如監(jiān)控每個(gè)方法的性能) 集中起來冲杀,放到一個(gè)統(tǒng)一的地方來控制和管理权谁。如果說,OOP如果是把問題劃分到單個(gè)模塊的話沪猴,那么AOP就是把涉及到眾多模塊的某一類問題進(jìn)行統(tǒng)一管理痹扇。
對(duì)比:
功能 | OOP | AOP |
---|---|---|
增加日志 | 所有功能模塊單獨(dú)添加担租,容易出錯(cuò) | 能夠?qū)⑼粋€(gè)關(guān)注點(diǎn)聚焦在一處解決 |
修改日志 | 功能代碼分散抵怎,不方便調(diào)試 | 能夠?qū)崿F(xiàn)一處修改奋救,處處生效 |
例如:在不改變 main 方法的同時(shí)通過代碼注入的方式達(dá)到目的
/**
* Before
*/
public class Test {
public static void main(String[] args) {
// do something
}
}
/**
* After
*/
public class Test {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// do something
long end = System.currentTimeMillis() - start;
}
}
二、AOP代碼注入時(shí)機(jī)
代碼注入主要注解機(jī)制反惕,根據(jù)注解時(shí)機(jī)的不同尝艘,主要分為運(yùn)行時(shí)、加載時(shí)和編譯時(shí)姿染。
運(yùn)行時(shí):你的代碼對(duì)增強(qiáng)代碼的需求很明確背亥,比如,必須使用動(dòng)態(tài)代理(這可以說并不是真正的代碼注入)。
加載時(shí):當(dāng)目標(biāo)類被Dalvik或者ART加載的時(shí)候修改才會(huì)被執(zhí)行隘梨。這是對(duì)Java字節(jié)碼文件或者Android的dex文件進(jìn)行的注入操作程癌。
編譯時(shí):在打包發(fā)布程序之前轴猎,通過向編譯過程添加額外的步驟來修改被編譯的類嵌莉。aspect切面編程正是運(yùn)用到編譯時(shí)
三、AOP的幾種實(shí)現(xiàn)方式
- Java 中的動(dòng)態(tài)代理捻脖,運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建 Proxy 類實(shí)例
- APT锐峭,注解處理器,編譯時(shí)生成 .java 代碼
- Javassist for Android:一個(gè)移植到Android平臺(tái)的非常知名的操縱字節(jié)碼的java庫可婶,對(duì) class 字節(jié)碼進(jìn)行修改
- AspectJ:和Java語言無縫銜接的面向切面的編程的擴(kuò)展工具(可用于Android)沿癞。
四,Android中使用 AspectJ
代表項(xiàng)目:Hugo(打印每個(gè)方法的執(zhí)行時(shí)間) sa-sdk-android(全埋點(diǎn)技術(shù))
(1)原理
AspectJ 意思就是Java的Aspect,Java的AOP矛渴。它的核心是ajc(編譯? aspectjtools)和 weaver(織入? aspectjweaver)椎扬。
ajc編譯?:基于Java編譯?之上的,它是用來編譯.aj文件具温,aspectj在Java編譯?的基礎(chǔ)上增加了一些它自己的關(guān)鍵字和方法蚕涤。因此,ajc也可以編譯Java代碼铣猩。
weaver織入?:為了在java編譯?上使用AspectJ而不依賴于Ajc編譯?揖铜,aspectJ 5出現(xiàn)了 @AspectJ,使用注釋的方式編寫AspectJ代碼达皿,可以在任何Java編譯?上使用天吓。
由于AndroidStudio默認(rèn)是沒有ajc編譯?的,所以在Android中使用@AspectJ來編寫峦椰。它在代碼的編譯期間掃描目標(biāo)程序龄寞,根據(jù)切點(diǎn)(PointCut)匹配,將開發(fā)者編寫的Aspect程序編織(Weave)到目標(biāo)程序的.class文件中,對(duì)目標(biāo)程序作了重構(gòu)(重構(gòu)單位是JoinPoint)汤功,目的就是建立目標(biāo)程序與Aspect程序的連接(獲得執(zhí)行的對(duì)象物邑、方法、參數(shù)等上下文信息)冤竹,從而達(dá)到AOP的目的拂封。
(2)AspectJ 術(shù)語
切面(Aspect):一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)實(shí)現(xiàn)可能另外橫切多個(gè)對(duì)象鹦蠕。其實(shí)就是共有功能的實(shí)現(xiàn)冒签。如日志切面、權(quán)限切面钟病、事務(wù)切面等萧恕。
通知(Advice):是切面的具體實(shí)現(xiàn)刚梭。以目標(biāo)方法為參照點(diǎn),根據(jù)放置的地方不同票唆,可分為
- 前置通知(Before)朴读、
- 后置通知(AfterReturning)、
- 異常通知(AfterThrowing)走趋、
- 最終通知(After)
- 環(huán)繞通知(Around)5種衅金。
在實(shí)際應(yīng)用中通常是切面類中的一個(gè)方法,具體屬于哪類通知由配置指定的簿煌。
切入點(diǎn)(Pointcut):用于定義通知應(yīng)該切入到哪些連接點(diǎn)上氮唯。不同的通知通常需要切入到不同的連接點(diǎn)上,這種精準(zhǔn)的匹配是由切入點(diǎn)的正則表達(dá)式來定義的姨伟。
連接點(diǎn)(JoinPoint):就是程序在運(yùn)行過程中能夠插入切面的地點(diǎn)惩琉。例如,方法調(diào)用夺荒、異常拋出或字段修改等瞒渠。
目標(biāo)對(duì)象(Target Object):包含連接點(diǎn)的對(duì)象,也被稱作被通知或被代理對(duì)象技扼。這些對(duì)象中已經(jīng)只剩下干干凈凈的核心業(yè)務(wù)邏輯代碼了伍玖,所有的共有功能等代碼則是等待AOP容器的切入。
AOP代理(AOP Proxy):將通知應(yīng)用到目標(biāo)對(duì)象之后被動(dòng)態(tài)創(chuàng)建的對(duì)象淮摔∷骄冢可以簡(jiǎn)單地理解為始赎,代理對(duì)象的功能等于目標(biāo)對(duì)象的核心業(yè)務(wù)邏輯功能加上共有功能和橙。代理對(duì)象對(duì)于使用者而言是透明的,是程序運(yùn)行過程中的產(chǎn)物造垛。
編織(Weaving):將切面應(yīng)用到目標(biāo)對(duì)象從而創(chuàng)建一個(gè)新的代理對(duì)象的過程魔招。這個(gè)過程可以發(fā)生在編譯期、類裝載期及運(yùn)行期五辽,當(dāng)然不同的發(fā)生點(diǎn)有著不同的前提條件办斑。譬如發(fā)生在編譯期的話,就要求有一個(gè)支持這種AOP實(shí)現(xiàn)的特殊編譯器(如AspectJ編譯器)杆逗;
發(fā)生在類裝載期乡翅,就要求有一個(gè)支持AOP實(shí)現(xiàn)的特殊類裝載器;只有發(fā)生在運(yùn)行期罪郊,則可直接通過Java語言的反射機(jī)制與動(dòng)態(tài)代理機(jī)制來動(dòng)態(tài)實(shí)現(xiàn)(如搖一搖)蠕蚜。
引入(Introduction):添加方法或字段到被通知的類。
(3)在Android項(xiàng)目中使用AspectJ
- gradle配置的方式:引入AspectJ是有點(diǎn)復(fù)雜的悔橄,需要引入大量的gradle命令配置有點(diǎn)麻煩靶累,在build文件中添加了一些腳本腺毫,文章出處:https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
- 使用 gradle 插件(也是對(duì) gradle 命令進(jìn)行了包裝):Jake Wharton 大神的 hugo 項(xiàng)目(一款日志打印的插件)
上海滬江團(tuán)隊(duì)的 gradle_plugin_android_aspectjx 一個(gè)基于AspectJ并在此基礎(chǔ)上擴(kuò)展出來可應(yīng)用于Android開發(fā)平臺(tái)的AOP框架,可作用于java源碼挣柬,class文件及jar包潮酒,同時(shí)支持kotlin的應(yīng)用。
AOP的用處非常廣邪蛔,從spring到Android急黎,各個(gè)地方都有使用,特別是在后端侧到,Spring中已經(jīng)使用的非常方便了叁熔,而且功能非常強(qiáng)大,但是在Android中床牧,AspectJ的實(shí)現(xiàn)是略閹割的版本荣回,并不是所有功能都支持,但對(duì)于一般的客戶端開發(fā)來說戈咳,已經(jīng)完全足夠用了心软。
(4)以 AspectJX 接入說明
- 首先,需要在項(xiàng)目根目錄的build.gradle中增加依賴:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
}
}
- 然后module項(xiàng)目的 build.gradle 中加入 AspectJ 的依賴:
apply plugin: 'android-aspectjx'
dependencies {
compile 'org.aspectj:aspectjrt:1.8.+'
}
aspectjx {
//排除所有package路徑中包含`android.support`的class文件及庫(jar文件)
exclude 'org.apache.httpcomponents'
exclude 'android.support'
}
- 具體配置參見github地址 https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
- 我們通過一段簡(jiǎn)單的代碼來了解下基本的使用方法和功能著蛙,新建一個(gè)AspectTest類文件删铃,代碼如下:
@Aspect
public class AspectTest {
private static final String TAG = "xuyisheng";
@Before("execution(* android.app.Activity.on**(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore: " + key);
}
@After("execution(* android.app.Activity.on**(..))")
public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodAfter: " + key);
}
@Around("execution(* android.app.Activity.on**(..))")
public void onActivityMethodAfter(ProceedingJoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore: " + key);
joinPoint.proceed();
Log.e(TAG, "onActivityMethodAfter: " + key);
}
}
在類的最開始,我們使用 @Aspect 注解來定義這樣一個(gè)AspectJ文件踏堡,編譯器在編譯的時(shí)候猎唁,就會(huì)自動(dòng)去解析,并不需要主動(dòng)去調(diào)用AspectJ類里面的代碼顷蟆。
具體的切面表達(dá)式可以參考execution表達(dá)式解析
(5)編織速度優(yōu)化建議
- 盡量使用精確的匹配規(guī)則,降低匹配時(shí)間诫隅。
- 排除不需要掃描的包。
通過這種方式編譯后帐偎,我們來看下生成的代碼是怎樣的逐纬。AspectJ的原理實(shí)際上是在編譯的時(shí)候,根據(jù)一定的規(guī)則解析削樊,然后插入一些代碼豁生,通過aspectj生成的代碼,會(huì)在Build目錄下:
[圖片上傳失敗...(image-fac24-1591768775090)]
四漫贞、總結(jié):
Aspectj:
- AspectJ除了hook之外甸箱,AspectJ還可以為目標(biāo)類添加變量,接口迅脐。另外芍殖,AspectJ也有抽象,繼承等各種更高級(jí)的玩法仪际。它能夠在編譯期間直接修改源代碼生成class围小。
- AspectJ語法比較多昵骤,但是掌握幾個(gè)簡(jiǎn)單常用的,就能實(shí)現(xiàn)絕大多數(shù)切片肯适,完全兼容Java(純Java語言開發(fā)变秦,然后使用AspectJ注解,簡(jiǎn)稱@AspectJ框舔。)
Kotlin無法生效的問題請(qǐng)參考此處