Android面向切面編程(AOP)

AOP簡介

什麼是AOP

AOP(Aspect Oriented Programming),中譯爲面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP(面向對象編程)的延續,是函數式編程的一種衍生範型。AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高代碼的靈活性和可拓展性。在Android中,AOP主要用於日誌記錄,權限控制,性能統計,安全控制,事務處理,異常處理,熱修復,埋點等功能。
AOP在日誌系統中的應用

AOP術語

通知、增強處理(Advice):就是你想要的功能,也就是上面說的日誌、耗時計算等。
連接點(JoinPoint):允許你通知(Advice)的地方,那可就真多了,基本每個方法的前、後(兩者都有也行),或拋出異常是時都可以是連接點(spring只支持方法連接點)。AspectJ還可以讓你在構造器或屬性注入時都行,不過一般情況下不會這麼做,只要記住,和方法有關的前前後後都是連接點。
切入點(Pointcut):上面說的連接點的基礎上,來定義切入點,你的一個類裏,有15個方法,那就有十幾個連接點了對吧,但是你並不想在所有方法附件都使用通知(使用叫織入,下面再說),你只是想讓其中幾個,在調用這幾個方法之前、之後或者拋出異常時乾點什麼,那麼就用切入點來定義這幾個方法,讓切點來篩選連接點,選中那幾個你想要的方法。
切面(Aspect):切面是通知和切入點的結合。現在發現了吧,沒連接點什麼事,連接點就是爲了讓你好理解切點搞出來的,明白這個概念就行了。通知說明了幹什麼和什麼時候幹(什麼時候通過before,after,around等AOP註解就能知道),而切入點說明了在哪幹(指定到底是哪個方法),這就是一個完整的切面定義。
織入(weaving): 把切面應用到目標對象來創建新的代理對象的過程。
目標對象 : 項目原始的Java組件。
AOP代理 : 由AOP框架生成java對象。

AOP與OOP區別

OOP 是面向名詞領域,AOP 面向動詞領域,OOP(面向對象編程)針對業務處理過程的實體及其屬性和行爲進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分,而AOP則是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。舉個例子,一個學生,通過OOP思想,我們可以抽象“學號”,“班級”這樣的屬性,“權限檢查”這一動作片斷進行劃分,則是AOP的目標領域。

AOP的實現方式和原理

AOP僅僅只是個概念,實現它的方式(工具和庫)有以下幾種:

AspectJ: 一個 JavaTM 語言的面向切面編程的無縫擴展(適用Android)。
Javassist for Android: 用於字節碼操作的知名 java 類庫 Javassist 的 Android 平臺移植版。
DexMaker: Dalvik 虛擬機上,在編譯期或者運行時生成代碼的 Java API。
ASMDEX: 一個類似 ASM 的字節碼操作庫,運行在Android平臺,操作Dex字節碼
AOP 方法作用時期比對
從上圖可知,AOP的實現主要有動態織入方式和動態織入方式,無論何種方法,均準尋AOP思想,知識插入時機不同,如果需要更深入瞭解,建議閱讀《一文讀懂 AOP | 你想要的最全面 AOP 方法探討

Android中使用AspectJ

AspectJ 意思就是Java的Aspect,Java的AOP。由於Android默認是沒有ajc編譯器的,所以在Android中使用@AspectJ來編寫(包括SpringAOP也是如此)。它在代碼的編譯期間掃描目標程序,根據切點匹配,將開發者編寫的Aspect程序編織到目標程序的.class文件中,對目標程序作了重構,目的就是建立目標程序與Aspect程序的連接,從而達到AOP的目的。簡單概括:

AspectJ是通過對目標工程的.class文件進行代碼注入的方式將通知(Advise)插入到目標代碼中。

使用江戶網的開源庫gradle_plugin_android_aspectjx 可以輕鬆接入AspectJ

配置環境
  1. 在項目根目錄的build.gradle裏依賴AspectJX
  2. 在module的build.gradle裏應用插件和庫
  3. 使用include,exclude命令來過濾需要處理的文件及排除某些文件(包括class文件及jar文件)
aspectjx {
//排除所有package路徑中包含`android.support`的class文件及庫(jar文件)
	exclude 'android.support'
}

此外,可以使用下面語句來關閉AspectJX功能,默認爲打開:

aspectjx {
//關閉AspectJX功能
	enabled false
}
常用註解介紹
  • @Aspect:聲明切面,標記類
  • @Pointcut:定義切點,標記方法
  • @Before:前置通知,切點之前執行
  • @Around:環繞通知,切點前後執行
  • @After(切點表達式):後置通知,切點之後執行 @AfterReturning(切點表達式):返回通知,切點方法返回結果之後執行
  • @AfterThrowing(切點表達式):異常通知,切點拋出異常時執行

除了@Aspect*,上述其他註解需要在切面類(@Aspect標註的類)中使用。

代碼實現

我們簡單創建一個 test() 函數作爲切點。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test();
    }

    public String test() {
        Log.d("AOPDemo","In test function(PointCut)");
        return "I am return";
    }
}

創建切面類TestAspectJ.java,主要只是點都標註在代碼中:

//@Aspect 聲明切面類
@Aspect
public class TestAspectJ {

    private static final String INSTANCE_METHOD_CALL =
            "execution(* com.saberhao.aopdemo.MainActivity.test())";

    //1.使用@Pointcut定義切點,方便重複利用
    @Pointcut(INSTANCE_METHOD_CALL) public void pointCut() {
    }

    //2.切點調用前調用
    @Before(INSTANCE_METHOD_CALL)
    public void beforeNoUsePointCut(JoinPoint point) {
        Log.d("AOPDemo","@Before");
    }

    @Before("pointCut()") //3.該註解等價於 @Before(INSTANCE_METHOD_CALL)
    public void before(JoinPoint point) {
        Log.d("AOPDemo","@Before PointCut");
    }

    //4.切點調用後調用
    @After("pointCut()")
    public void after(JoinPoint point) {
        Log.d("AOPDemo","@After PointCut");
    }

    //5.切點有返回返回值後調用
    @AfterReturning(pointcut = "pointCut()", returning = "returnValue") //"pointcut" 同 "value"
    public void afterReturning(JoinPoint point, Object returnValue){
        Log.d("AOPDemo","@AfterReturning PointCut,return value is "+ returnValue);
    }

輸出如下:

com.saberhao.aopdemo D/AOPDemo: @Before
com.saberhao.aopdemo D/AOPDemo: @Before PointCut
com.saberhao.aopdemo D/AOPDemo: In test function(PointCut)
com.saberhao.aopdemo D/AOPDemo: @After PointCut
com.saberhao.aopdemo D/AOPDemo: @AfterReturning PointCut,return value is I am return

需要注意的是,@Around環繞通知會攔截原方法內容的執行,我們需要手動放行纔可以:

@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
     Log.d("AOPDemo","@Around Before");
     //手動放行原方法
     Object result = joinPoint.proceed();
     Log.d("AOPDemo","@Around After");
     return result;
}

執行效果如下:

com.saberhao.aopdemo D/AOPDemo: @Around Before
com.saberhao.aopdemo D/AOPDemo: In test function(PointCut)
com.saberhao.aopdemo D/AOPDemo: @Around After

如果要測試 @AfterThrowing效果,需要修改test() 函數,拋出異常:

public String test() {
    Log.d("AOPDemo","In test function(PointCut)");
    //測試@AfterThrowing時,手動拋出異常
    throw new NullPointerException("check pointcut throwing");
    //return "I am return";
}

測試效果:

com.saberhao.aopdemo D/AOPDemo: @Before
com.saberhao.aopdemo D/AOPDemo: @Before PointCut
com.saberhao.aopdemo D/AOPDemo: In test function(PointCut)
com.saberhao.aopdemo D/AOPDemo: @After PointCut
com.saberhao.aopdemo D/AOPDemo: @AfterThrowing,ex: check pointcut throwing

這裏只是簡單引入,需要深入瞭解,可以參考:AOP之@AspectJ技術原理詳解

Demo已經上傳到GitHub, 點擊 這裏 獲取

其他

AOP技術廣泛應用於開源代碼中,比較有名的有Retrofit, 實現AOP,也可以考慮使用APT,使用JavaPoet方式可以方便引入,可以參考ButterKnife的實現,後續可以獨立開一篇說說~

參考
AOP (面向切面編程) – 百度百科
Comparing Spring AOP and AspectJ
Android面向切面編程(AOP)
一文讀懂 AOP | 你想要的最全面 AOP 方法探討
AOP之@AspectJ技術原理詳解

發佈了61 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章