AOP(Aspect-Oriented Programming,面向切面編程),可謂是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。AOP技術利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。所謂「方面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降 模塊間的耦合度,並有利於將來的可操做性和可維護性。Android相關AOP經常使用的方法有 JNI HOOK 和 靜態織入,本文以靜態織入的方式之一AspectJ來進行講解使用。(在編譯期織入,切面直接以字節碼的形式編譯到目標字節碼文件中,這要求使用特殊的 Java 編譯器。)android
對於Java後端來講最多見的場景應該就是日誌記錄、檢查用戶權限、參數校驗、事務處理、緩存等等了,而對於Android來講也是比較普遍的(適用與Java的一樣都適用於Android),例如常見的有記錄日誌、檢查權限申請、判斷用戶登陸狀態、檢查網絡狀態、過濾重複點擊等等,能夠根據項目實際須要的一些業務場景來自定義你所須要的。git
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'
}
}
複製代碼
@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註解的方法,並獲取註解中的值,去作相應處理。
複製代碼
一、定義註解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);
}
複製代碼
一、定義註解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。