RxCache 是一個(gè)支持 Java 和 Android 的 Local Cache 猖凛。
之前的文章《給 Java 和 Android 構(gòu)建一個(gè)簡(jiǎn)單的響應(yīng)式Local Cache》舔琅、《RxCache 整合 Android 的持久層框架 greenDAO俺夕、Room》曾詳細(xì)介紹過它借卧。
目前,對(duì)框架增加一些 Annotation 以及 Cache 替換算法举哟。
一. 基于 Annotation 完成緩存操作
類似 Retrofit 風(fēng)格的方式俯渤,支持通過標(biāo)注 Annotation 來完成緩存的操作。
例如先定義一個(gè)接口侨嘀,用于定義緩存的各種操作臭挽。
public interface Provider {
@CacheKey("user")
@CacheMethod(methodType = MethodType.GET)
<T> Record<T> getData(@CacheClass Class<T> clazz);
@CacheKey("user")
@CacheMethod(methodType = MethodType.SAVE)
@CacheLifecycle(duration = 2000)
void putData(@CacheValue User user);
@CacheKey("user")
@CacheMethod(methodType = MethodType.REMOVE)
void removeUser();
@CacheKey("test")
@CacheMethod(methodType = MethodType.GET, observableType = ObservableType.MAYBE)
<T> Maybe<Record<T>> getMaybe(@CacheClass Class<T> clazz);
}
通過 CacheProvider 創(chuàng)建該接口,然后可以完成各種緩存操作咬腕。
public class TestCacheProvider {
public static void main(String[] args) {
RxCache.config(new RxCache.Builder());
RxCache rxCache = RxCache.getRxCache();
CacheProvider cacheProvider = new CacheProvider.Builder().rxCache(rxCache).build();
Provider provider = cacheProvider.create(Provider.class);
User u = new User();
u.name = "tony";
u.password = "123456";
provider.putData(u); // 將u存入緩存中
Record<User> record = provider.getData(User.class); // 從緩存中獲取key="user"的數(shù)據(jù)
if (record!=null) {
System.out.println(record.getData().name);
}
provider.removeUser(); // 從緩存中刪除key="user"的數(shù)據(jù)
record = provider.getData(User.class);
if (record==null) {
System.out.println("record is null");
}
User u2 = new User();
u2.name = "tony2";
u2.password = "000000";
rxCache.save("test",u2);
Maybe<Record<User>> maybe = provider.getMaybe(User.class); // 從緩存中獲取key="test"的數(shù)據(jù)欢峰,返回的類型為Maybe
maybe.subscribe(new Consumer<Record<User>>() {
@Override
public void accept(Record<User> userRecord) throws Exception {
User user = userRecord.getData();
if (user!=null) {
System.out.println(user.name);
System.out.println(user.password);
}
}
});
}
}
CacheProvider 核心是 create(),它通過動(dòng)態(tài)代理來創(chuàng)建Provider涨共。
public <T> T create(Class<T> clazz) {
CacheProxy cacheProxy = new CacheProxy(rxCache);
try {
return (T) Proxy.newProxyInstance(CacheProvider.class.getClassLoader(), new Class[]{clazz}, cacheProxy);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
其中纽帖,CacheProxy 實(shí)現(xiàn)了 InvocationHandler 接口,是創(chuàng)建代理類的調(diào)用處理器煞赢。
package com.safframework.rxcache.proxy;
import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.proxy.annotation.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Created by tony on 2018/10/30.
*/
public class CacheProxy implements InvocationHandler {
RxCache rxCache;
public CacheProxy(RxCache rxCache) {
this.rxCache = rxCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
CacheMethod cacheMethod = method.getAnnotation(CacheMethod.class);
CacheKey cacheKey = method.getAnnotation(CacheKey.class);
CacheLifecycle cacheLifecycle = method.getAnnotation(CacheLifecycle.class);
Annotation[][] allParamsAnnotations = method.getParameterAnnotations();
Class cacheClazz = null;
Object cacheValue = null;
if (allParamsAnnotations != null) {
for (int i = 0; i < allParamsAnnotations.length; i++) {
Annotation[] paramAnnotations = allParamsAnnotations[i];
if (paramAnnotations != null) {
for (Annotation annotation : paramAnnotations) {
if (annotation instanceof CacheClass) {
cacheClazz = (Class) args[i];
}
if (annotation instanceof CacheValue) {
cacheValue = args[i];
}
}
}
}
}
if (cacheMethod!=null) {
MethodType methodType = cacheMethod.methodType();
long duration = -1;
if (cacheLifecycle != null) {
duration = cacheLifecycle.duration();
}
if (methodType == MethodType.GET) {
ObservableType observableType = cacheMethod.observableType();
if (observableType==ObservableType.NOUSE) {
return rxCache.get(cacheKey.value(),cacheClazz);
} else if (observableType == ObservableType.OBSERVABLE){
return rxCache.load2Observable(cacheKey.value(),cacheClazz);
} else if (observableType==ObservableType.FLOWABLE) {
return rxCache.load2Flowable(cacheKey.value(),cacheClazz);
} else if (observableType==ObservableType.SINGLE) {
return rxCache.load2Single(cacheKey.value(),cacheClazz);
} else if (observableType==ObservableType.MAYBE) {
return rxCache.load2Maybe(cacheKey.value(),cacheClazz);
}
} else if (methodType == MethodType.SAVE) {
rxCache.save(cacheKey.value(),cacheValue,duration);
} else if (methodType == MethodType.REMOVE) {
rxCache.remove(cacheKey.value());
}
}
return null;
}
}
CacheProxy 的 invoke() 方法先獲取 Method 所使用的 Annotation抛计,包括CacheMethod、CacheKey照筑、CacheLifecycle吹截。
其中瘦陈,CacheMethod 是最核心的 Annotation,它取決于 rxCache 使用哪個(gè)方法波俄。CacheMethod 支持的方法類型包括:獲取晨逝、保存、刪除緩存懦铺。當(dāng) CacheMethod 的 methodType 是 GET 類型捉貌,則可能會(huì)返回 RxJava 的各種 Observable 類型,或者還是返回所存儲(chǔ)的對(duì)象類型冬念。
CacheKey 是任何方法都需要使用的 Annotation趁窃。CacheLifecycle 只有保存緩存時(shí)才會(huì)使用。
二. 支持多種緩存替換算法
RxCache 包含了兩級(jí)緩存: Memory 和 Persistence 急前。
Memory 的默認(rèn)實(shí)現(xiàn) FIFOMemoryImpl醒陆、LRUMemoryImpl、LFUMemoryImpl 分別使用 FIFO裆针、LRU刨摩、LFU 算法來緩存數(shù)據(jù)。
2.1 FIFO
通過使用 LinkedList 存放緩存的 keys世吨,ConcurrentHashMap 存放緩存的數(shù)據(jù)澡刹,就可以實(shí)現(xiàn) FIFO。
2.2 LRU
LRU是Least Recently Used的縮寫耘婚,即最近最少使用罢浇,常用于頁面置換算法,是為虛擬頁式存儲(chǔ)管理服務(wù)的沐祷。
使用 ConcurrentHashMap 和 ConcurrentLinkedQueue 實(shí)現(xiàn)該算法己莺。如果某個(gè)數(shù)據(jù)已經(jīng)存放在緩存中,則從 queue 中刪除并添加到 queue 的第一個(gè)位置戈轿。如果緩存已滿,則從 queue 中刪除最后面的數(shù)據(jù)阵子。并把新的數(shù)據(jù)添加到緩存思杯。
public class LRUCache<K,V> {
private Map<K,V> cache = null;
private AbstractQueue<K> queue = null;
private int size = 0;
public LRUCache() {
this(Constant.DEFAULT_CACHE_SIZE);
}
public LRUCache(int size) {
this.size = size;
cache = new ConcurrentHashMap<K,V>(size);
queue = new ConcurrentLinkedQueue<K>();
}
public boolean containsKey(K key) {
return cache.containsKey(key);
}
public V get(K key) {
//Recently accessed, hence move it to the tail
queue.remove(key);
queue.add(key);
return cache.get(key);
}
public V getSilent(K key) {
return cache.get(key);
}
public void put(K key, V value) {
//ConcurrentHashMap doesn't allow null key or values
if(key == null || value == null) throw new RxCacheException("key is null or value is null");
if(cache.containsKey(key)) {
queue.remove(key);
}
if(queue.size() >= size) {
K lruKey = queue.poll();
if(lruKey != null) {
cache.remove(lruKey);
}
}
queue.add(key);
cache.put(key,value);
}
/**
* 獲取最近最少使用的值
* @return
*/
public V getLeastRecentlyUsed() {
K remove = queue.remove();
queue.add(remove);
return cache.get(remove);
}
public void remove(K key) {
cache.remove(key);
queue.remove(key);
}
public void clear() {
cache.clear();
queue.clear();
}
......
}
2.3 LFU
LFU是Least Frequently Used的縮寫,即最近最不常用使用挠进。
看上去跟 LRU 類似色乾,其實(shí)它們并不相同。LRU 是淘汰最長(zhǎng)時(shí)間未被使用的數(shù)據(jù)领突,而 LFU 是淘汰一定時(shí)期內(nèi)被訪問次數(shù)最少的數(shù)據(jù)暖璧。
LFU 會(huì)記錄數(shù)據(jù)在一定時(shí)間內(nèi)的使用次數(shù)。稍顯復(fù)雜感興趣的可以閱讀 RxCache 中相關(guān)的源碼君旦。
三. 總結(jié)
RxCache 大體已經(jīng)完成澎办,初步可以使用嘲碱。
RxCache github 地址:https://github.com/fengzhizi715/RxCache
Android 版本的 RxCache github 地址:https://github.com/fengzhizi715/RxCache4a
對(duì)于 Android ,除了支持常見的持久層框架之外局蚀,還支持 RxCache 轉(zhuǎn)換成 LiveData麦锯。如果想要跟 Retrofit 結(jié)合,可以通過 RxCache 的 transform 策略琅绅。
對(duì)于Java 后端扶欣,RxCache 只是一個(gè)本地緩存,不適合存放大型的數(shù)據(jù)千扶。但是其內(nèi)置的 Memory 層包含了多種緩存替換算法料祠,不用內(nèi)置的 Memory 還可以使用 Guava Cache、Caffeine 澎羞。