一. 為何要創(chuàng)建這個庫
首先遗嗽,Local Cache 不是類似于 Redis秸仙、Couchbase、Memcached 這樣的分布式 Cache滤港。Local Cache 適用于在單機環(huán)境下鼠冕,對訪問頻率高添寺、更新次數(shù)少的數(shù)據(jù)進行存放。因此懈费,Local Cache 不適合存放大量的數(shù)據(jù)计露。
Local Cache 特別適合于 App,也適合在 Java 的某些場景下使用。
我們的 App 使用 Retrofit 作為網(wǎng)絡框架票罐,并且大量使用 RxJava叉趣,因此我考慮創(chuàng)建一個 RxCache 來緩存一些必要的數(shù)據(jù)。
RxCache 地址:https://github.com/fengzhizi715/RxCache
二. 如何構建 RxCache
2.1 RxCache 的基本方法
對于 Local Cache该押,最重要是需要有以下的這些方法:
<T> Record<T> get(String key, Type type);
<T> void save(String key, T value);
<T> void save(String key, T value, long expireTime);
boolean containsKey(String key);
Set<String> getAllKeys();
void remove(String key);
void clear();
其中疗杉,有一個 save() 方法包含了失效時間的參數(shù)expireTime,這對于 Local Cache 是比較重要的一個方法蚕礼,超過這個時間烟具,這個數(shù)據(jù)將會失效。
既然是 RxCache奠蹬,對于獲取數(shù)據(jù)肯定需要類似這樣的方法:
<T> Observable<Record<T>> load2Observable(final String key, final Type type) ;
<T> Flowable<Record<T>> load2Flowable(final String key, final Type type);
<T> Single<Record<T>> load2Single(final String key, final Type type);
<T> Maybe<Record<T>> load2Maybe(final String key, final Type type);
也需要一些 Transformer 的方法朝聋,將 RxJava 的被觀察者進行轉換。在 RxCache 中囤躁,包含了一些默認的 Transformer 策略冀痕,特別是使用 Retrofit 和 RxJava 時,可以考慮結合這些策略來緩存數(shù)據(jù)狸演。
以 CacheFirstStrategy 為例:
/**
* 緩存優(yōu)先的策略言蛇,緩存取不到時取接口的數(shù)據(jù)。
* Created by tony on 2018/9/30.
*/
public class CacheFirstStrategy implements ObservableStrategy,
FlowableStrategy,
MaybeStrategy {
@Override
public <T> Publisher<Record<T>> execute(RxCache rxCache, String key, Flowable<T> source, Type type) {
Flowable<Record<T>> cache = rxCache.<T>load2Flowable(key, type);
Flowable<Record<T>> remote = source
.map(new Function<T, Record<T>>() {
@Override
public Record<T> apply(@NonNull T t) throws Exception {
rxCache.save(key, t);
return new Record<>(Source.CLOUD, key, t);
}
});
return cache.switchIfEmpty(remote);
}
@Override
public <T> Maybe<Record<T>> execute(RxCache rxCache, String key, Maybe<T> source, Type type) {
Maybe<Record<T>> cache = rxCache.<T>load2Maybe(key, type);
Maybe<Record<T>> remote = source
.map(new Function<T, Record<T>>() {
@Override
public Record<T> apply(@NonNull T t) throws Exception {
rxCache.save(key, t);
return new Record<>(Source.CLOUD, key, t);
}
});
return cache.switchIfEmpty(remote);
}
@Override
public <T> Observable<Record<T>> execute(RxCache rxCache, String key, Observable<T> source, Type type) {
Observable<Record<T>> cache = rxCache.<T>load2Observable(key, type);
Observable<Record<T>> remote = source
.map(new Function<T, Record<T>>() {
@Override
public Record<T> apply(@NonNull T t) throws Exception {
rxCache.save(key, t);
return new Record<>(Source.CLOUD, key, t);
}
});
return cache.switchIfEmpty(remote);
}
}
2.2 Memory
RxCache 包含了兩級緩存: Memory 和 Persistence 严沥。
Memory:
package com.safframework.rxcache.memory;
import com.safframework.rxcache.domain.Record;
import java.util.Set;
/**
* Created by tony on 2018/9/29.
*/
public interface Memory {
<T> Record<T> getIfPresent(String key);
<T> void put(String key, T value);
<T> void put(String key, T value, long expireTime);
Set<String> keySet();
boolean containsKey(String key);
void evict(String key);
void evictAll();
}
它的默認實現(xiàn) DefaultMemoryImpl 使用 ConcurrentHashMap 來緩存數(shù)據(jù)。
在 extra 模塊還有 Guava Cache中姜、Caffeine 的實現(xiàn)消玄。它們都是成熟的 Local Cache,如果不想使用 DefaultMemoryImpl 丢胚,完全可以使用 extra 模塊成熟的替代方案翩瓜。
2.3 Persistence
Persistence 的接口跟 Memory 很類似:
package com.safframework.rxcache.persistence;
import com.safframework.rxcache.domain.Record;
import java.lang.reflect.Type;
import java.util.List;
/**
* Created by tony on 2018/9/28.
*/
public interface Persistence {
<T> Record<T> retrieve(String key, Type type);
<T> void save(String key, T value);
<T> void save(String key, T value, long expireTime);
List<String> allKeys();
boolean containsKey(String key);
void evict(String key);
void evictAll();
}
由于,考慮到持久層可能包括 Disk携龟、DB兔跌。于是單獨抽象了一個 Disk 接口繼承 Persistence。
在 Disk 的實現(xiàn)類 DiskImpl 中峡蟋,它的構造方法注入了 Converter 接口:
public class DiskImpl implements Disk {
private File cacheDirectory;
private Converter converter;
public DiskImpl(File cacheDirectory,Converter converter) {
this.cacheDirectory = cacheDirectory;
this.converter = converter;
}
......
}
Converter 接口用于對象儲存到文件的序列化和反序列化坟桅,目前支持 Gson 和 FastJSON。
Converter 的抽象實現(xiàn)類 AbstractConverter 的構造方法注入了 Encryptor 接口:
public abstract class AbstractConverter implements Converter {
private Encryptor encryptor;
public AbstractConverter() {
}
public AbstractConverter(Encryptor encryptor) {
this.encryptor = encryptor;
}
......
}
Encryptor 接口用于將存儲到 Disk 上的數(shù)據(jù)進行加密和解密蕊蝗,目前 RxCache 支持 AES128 和 DES 兩種加密方式仅乓。不使用 Encryptor 接口,則存儲到 Disk 上的數(shù)據(jù)是明文蓬戚,也就是一串json字符串夸楣。
三. 支持 Java
在 example 模塊下,包括了一些常見 Java 使用的例子。
例如豫喧,最簡單的使用:
import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.domain.Record;
import domain.User;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
/**
* Created by tony on 2018/9/29.
*/
public class Test {
public static void main(String[] args) {
RxCache.config(new RxCache.Builder());
RxCache rxCache = RxCache.getRxCache();
User u = new User();
u.name = "tony";
u.password = "123456";
rxCache.save("test",u);
Observable<Record<User>> observable = rxCache.load2Observable("test", User.class);
observable.subscribe(new Consumer<Record<User>>() {
@Override
public void accept(Record<User> record) throws Exception {
User user = record.getData();
System.out.println(user.name);
System.out.println(user.password);
}
});
}
}
帶 ExpireTime 的緩存測試:
import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.domain.Record;
import domain.User;
/**
* Created by tony on 2018/10/5.
*/
public class TestWithExpireTime {
public static void main(String[] args) {
RxCache.config(new RxCache.Builder());
RxCache rxCache = RxCache.getRxCache();
User u = new User();
u.name = "tony";
u.password = "123456";
rxCache.save("test",u,2000);
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Record<User> record = rxCache.get("test", User.class);
if (record==null) {
System.out.println("record is null");
}
}
}
跟 Spring 整合并且 Memory 的實現(xiàn)使用 GuavaCacheImpl:
import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.extra.memory.GuavaCacheImpl;
import com.safframework.rxcache.memory.Memory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
/**
* Created by tony on 2018/10/5.
*/
@Configurable
public class ConfigWithGuava {
@Bean
public Memory guavaCache(){
return new GuavaCacheImpl(100);
}
@Bean
public RxCache.Builder rxCacheBuilder(){
return new RxCache.Builder().memory(guavaCache());
}
@Bean
public RxCache rxCache() {
RxCache.config(rxCacheBuilder());
return RxCache.getRxCache();
}
}
測試一下剛才的整合:
import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.domain.Record;
import domain.User;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* Created by tony on 2018/10/5.
*/
public class TestWithGuava {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithGuava.class);
RxCache rxCache = ctx.getBean(RxCache.class);
User u = new User();
u.name = "tony";
u.password = "123456";
rxCache.save("test",u);
Observable<Record<User>> observable = rxCache.load2Observable("test", User.class);
observable.subscribe(new Consumer<Record<User>>() {
@Override
public void accept(Record<User> record) throws Exception {
User user = record.getData();
System.out.println(user.name);
System.out.println(user.password);
}
});
}
}
四. 支持 Android
為了更好地支持 Android石洗,我還單獨創(chuàng)建了一個項目 RxCache4a: https://github.com/fengzhizi715/RxCache4a
它包含了一個基于 LruCache 的 Memory 實現(xiàn),以及一個基于 MMKV(騰訊開源的key
-value存儲框架) 的 Persistence 實現(xiàn)紧显。
我們目前 App 采用了如下的 MVVM 架構來傳輸數(shù)據(jù):
未來讲衫,希望能夠通過 RxCache 來整合 Repository 這一層。
五. 總結
目前鸟妙,RxCache 完成了大體的框架焦人,初步可用,接下來打算增加一些 Annotation重父,方便其使用花椭。