Android開發之AOP編程

1、簡介:

AOP(Aspect-Oriented Programming,面向切面編程),可謂是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。AOP技術利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。所謂「方面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降 模塊間的耦合度,並有利於將來的可操做性和可維護性。Android相關AOP經常使用的方法有 JNI HOOK 和 靜態織入,本文以靜態織入的方式之一AspectJ來進行講解使用。(在編譯期織入,切面直接以字節碼的形式編譯到目標字節碼文件中,這要求使用特殊的 Java 編譯器。)android

2、使用場景

對於Java後端來講最多見的場景應該就是日誌記錄、檢查用戶權限、參數校驗、事務處理、緩存等等了,而對於Android來講也是比較普遍的(適用與Java的一樣都適用於Android),例如常見的有記錄日誌、檢查權限申請、判斷用戶登陸狀態、檢查網絡狀態、過濾重複點擊等等,能夠根據項目實際須要的一些業務場景來自定義你所須要的。git

3、實踐

一、添加依賴
apply plugin: 'android-aspectjx'

dependencies {
    ...
    compile 'org.aspectj:aspectjrt:1.8.9'
}
複製代碼

項目跟目錄的gradle腳本中加入github

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        //此處推薦該庫,由滬江出品,可免去配置各類複雜任務
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
    }
}
複製代碼
二、AOP註解與使用
@Aspect:聲明切面,標記類
@Pointcut(切點表達式):定義切點,標記方法
@Before(切點表達式):前置通知,切點以前執行
@Around(切點表達式):環繞通知,切點先後執行
@After(切點表達式):後置通知,切點以後執行
@AfterReturning(切點表達式):返回通知,切點方法返回結果以後執行
@AfterThrowing(切點表達式):異常通知,切點拋出異常時執行
複製代碼

切點表達式的示例:編程

如:execution(@com.xxx.aop.TimeLog *.(..))
切點表達式的組成:
execution(@註解 訪問權限 返回值的類型 包名.函數名(參數))
複製代碼

下面簡單列舉兩個簡單例子:json

@Before("execution(@com.xxx.aop.activity * *(..))")
public void before(JoinPoint point) {
    Log.i(TAG, "method excute before...");
}
匹配activity包下的全部方法,在方法執行前輸出日誌
複製代碼
@Around("execution(@cn.com.xxx.Async * *(..))")
public void doAsyncMethod(ProceedingJoinPoint joinPoint) {
    Log.i(TAG, "method excute before...");
    joinPoint.proceed();
    Log.i(TAG, "method excute after...");
}
匹配帶有Async註解的方法,在方法執行先後分別輸出日誌
複製代碼
@Around("execution(@cn.com.xxx.Async * *(..)) && @annotation(async)")
public void doAsyncMethod(ProceedingJoinPoint joinPoint, Async async) {
    Log.i(TAG, "value>>>>>"+async.value());
    <!--to do somethings-->
    joinPoint.proceed();
    <!--to do somethings-->
}
//注意:@annotation(xxx)必須與下面的的參數值對應
匹配帶有Async註解的方法,並獲取註解中的值,去作相應處理。
複製代碼
三、實際應用舉例
場景1、利用註解實現緩存處理

一、定義註解Cache以下:後端

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cache {

    String key(); //緩存的key

    int expiry() default -1; // 過時時間,單位是秒
}
複製代碼

二、編寫Aspect實現緩存

@Aspect
public class CacheAspect {
    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.Cache * *(..))";

    @Pointcut(POINTCUT_METHOD)
    public void onCacheMethod() {
    }

    @Around("onCacheMethod() && @annotation(cache)")
    public Object doCacheMethod(ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {
        //獲取註解中的key
        String key = cache.key();
        //獲取註解中的過時時間
        int expiry = cache.expiry();
        //執行當前註解的方法(放行)
        Object result = joinPoint.proceed();
        //方法執行後進行緩存(緩存對象必須是方法返回值)
        ACache aCache = ACache.get(AopArms.getContext());
        if (expiry>0) {
            aCache.put(key,(Serializable)result,expiry);
        } else {
            aCache.put(key,(Serializable)result);
        }
        return result;
    }
}
複製代碼

此處引用ACache該項目中的緩存實現,僅一個Acache文件,我的以爲仍是比較好用的,固然你也能夠本身去實現。 三、測試安全

public static void main(String[] args) {
        initData();
        getUser();
    }
    
    //緩存數據
    @Cache(key = "userList")
    private ArrayList<User> initData() {
        ArrayList<User> list = new ArrayList<>();
        for (int i=0; i<5; i++){
            User user = new User();
            user.setName("艾神一不當心:"+i);
            user.setPassword("密碼:"+i);
            list.add(user);
        }
        return list;
    }
    
    //獲取緩存
    private void getUser() {
        ArrayList<User> users = ACache.get(this).getAsList("userList", User.class);
        Log.e(TAG, "getUser: "+users);
    }
    
複製代碼
場景2、利用註解實現緩存移除

一、定義註解CacheEvict以下:bash

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheEvict {

    //須要移除的key
    String key();

    // 緩存的清除是否在方法以前執行, 默認表明緩存清除操做是在方法執行以後執行;若是出現異常緩存就不會清除
    boolean beforeInvocation() default false;
    
    //是否清空全部緩存
    boolean allEntries() default false;
}
複製代碼

二、編寫Aspect實現網絡

@Aspect
public class CacheEvictAspect {
    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.CacheEvict * *(..))";

    //切點位置,全部的CacheEvict處
    @Pointcut(POINTCUT_METHOD)
    public void onCacheEvictMethod() {
    }
    
    //環繞處理,並拿到CacheEvict註解值
    @Around("onCacheEvictMethod() && @annotation(cacheEvict)")
    public Object doCacheEvictMethod(ProceedingJoinPoint joinPoint, CacheEvict cacheEvict) throws Throwable {
        String key = cacheEvict.key();
        boolean beforeInvocation = cacheEvict.beforeInvocation();
        boolean allEntries = cacheEvict.allEntries();
        ACache aCache = ACache.get(AopArms.getContext());
        Object result = null;
        if (allEntries){
            //若是是所有清空,則key不須要有值
            if (!TextUtils.isEmpty(key))
                throw new IllegalArgumentException("Key cannot have value when cleaning all caches");
            aCache.clear();
        }
        if (beforeInvocation){
            //方法執行前,移除緩存
            aCache.remove(key);
            result = joinPoint.proceed();
        }else {
            //方法執行後,移除緩存,若是出現異常緩存就不會清除(推薦)
            result = joinPoint.proceed();
            aCache.remove(key);
        }
        return result;
    }
}
複製代碼

三、測試

public static void main(String[] args) {
        removeUser();
        getUser();
    }
    
    //移除緩存數據
    @CacheEvict(key = "userList")
    private void removeUser() {
        Log.e(TAG, "removeUser: >>>>");
    }
    
    //獲取緩存
    private void getUser() {
        ArrayList<User> users = ACache.get(this).getAsList("userList", User.class);
        Log.e(TAG, "getUser: "+users);
    }
複製代碼

能夠看到執行結果:

最後推薦本人寫的一個基於AOP的註解框架AopArms,用法及其簡單,參考一款基於AOP的Android註解框架,裏面編寫了Android開發中經常使用的一套註解,如日誌、異步處理、緩存、SP、延遲操做、定時任務、重試機制、try-catch安全機制、過濾頻繁點擊等,後續還會有更多更強大的註解功能加入,歡迎star。

相關文章
相關標籤/搜索