给 Java 和 Android 构建一个简单的响应式Local Cache

640?wx_fmt=png

一. 为何要创建这个库

首先,Local Cache 不是类似于 Redis、Couchbase、Memcached 这样的分布式 Cache。Local Cache 适用于在单机环境下,对访问频率高、更新次数少的数据进行存放。因此,Local Cache 不适合存放大量的数据。

Local Cache 特别适合于 App,也适合在 Java 的某些场景下使用。

我们的 App 使用 Retrofit 作为网络框架,并且大量使用 RxJava,因此我考虑创建一个 RxCache 来缓存一些必要的数据。

RxCache 地址:https://github.com/fengzhizi715/RxCache

二. 如何构建 RxCache

2.1 RxCache 的基本方法

对于 Local Cache,最重要是需要有以下的这些方法:

  1. <T> Record<T> get(String key, Type type);

  2. <T> void save(String key, T value);

  3. <T> void save(String key, T value, long expireTime);

  4. boolean containsKey(String key);

  5. Set<String> getAllKeys();

  6. void remove(String key);

  7. void clear();

其中,有一个 save() 方法包含了失效时间的参数expireTime,这对于 Local Cache 是比较重要的一个方法,超过这个时间,这个数据将会失效。

既然是 RxCache,对于获取数据肯定需要类似这样的方法:

  1. <T> Observable<Record<T>> load2Observable(final String key, final Type type) ;

  2. <T> Flowable<Record<T>> load2Flowable(final String key, final Type type);

  3. <T> Single<Record<T>> load2Single(final String key, final Type type);

  4. <T> Maybe<Record<T>> load2Maybe(final String key, final Type type);

也需要一些 Transformer 的方法,将 RxJava 的被观察者进行转换。在 RxCache 中,包含了一些默认的 Transformer 策略,特别是使用 Retrofit 和 RxJava 时,可以考虑结合这些策略来缓存数据。

以 CacheFirstStrategy 为例:

  1. /**

  2. * 缓存优先的策略,缓存取不到时取接口的数据。

  3. * Created by tony on 2018/9/30.

  4. */

  5. public class CacheFirstStrategy implements ObservableStrategy,

  6.        FlowableStrategy,

  7.        MaybeStrategy  {

  8.    @Override

  9.    public <T> Publisher<Record<T>> execute(RxCache rxCache, String key, Flowable<T> source, Type type) {

  10.        Flowable<Record<T>> cache = rxCache.<T>load2Flowable(key, type);

  11.        Flowable<Record<T>> remote = source

  12.                .map(new Function<T, Record<T>>() {

  13.                    @Override

  14.                    public Record<T> apply(@NonNull T t) throws Exception {

  15.                        rxCache.save(key, t);

  16.                        return new Record<>(Source.CLOUD, key, t);

  17.                    }

  18.                });

  19.        return cache.switchIfEmpty(remote);

  20.    }

  21.    @Override

  22.    public <T> Maybe<Record<T>> execute(RxCache rxCache, String key, Maybe<T> source, Type type) {

  23.        Maybe<Record<T>> cache = rxCache.<T>load2Maybe(key, type);

  24.        Maybe<Record<T>> remote = source

  25.                .map(new Function<T, Record<T>>() {

  26.                    @Override

  27.                    public Record<T> apply(@NonNull T t) throws Exception {

  28.                        rxCache.save(key, t);

  29.                        return new Record<>(Source.CLOUD, key, t);

  30.                    }

  31.                });

  32.        return cache.switchIfEmpty(remote);

  33.    }

  34.    @Override

  35.    public <T> Observable<Record<T>> execute(RxCache rxCache, String key, Observable<T> source, Type type) {

  36.        Observable<Record<T>> cache = rxCache.<T>load2Observable(key, type);

  37.        Observable<Record<T>> remote = source

  38.                .map(new Function<T, Record<T>>() {

  39.                    @Override

  40.                    public Record<T> apply(@NonNull T t) throws Exception {

  41.                        rxCache.save(key, t);

  42.                        return new Record<>(Source.CLOUD, key, t);

  43.                    }

  44.                });

  45.        return cache.switchIfEmpty(remote);

  46.    }

  47. }

2.2 Memory

RxCache 包含了两级缓存: Memory 和 Persistence 。

640?wx_fmt=png

Memory:

  1. package com.safframework.rxcache.memory;

  2. import com.safframework.rxcache.domain.Record;

  3. import java.util.Set;

  4. /**

  5. * Created by tony on 2018/9/29.

  6. */

  7. public interface Memory {

  8.    <T> Record<T> getIfPresent(String key);

  9.    <T> void put(String key, T value);

  10.    <T> void put(String key, T value, long expireTime);

  11.    Set<String> keySet();

  12.    boolean containsKey(String key);

  13.    void evict(String key);

  14.    void evictAll();

  15. }

它的默认实现 DefaultMemoryImpl 使用 ConcurrentHashMap 来缓存数据。

在 extra 模块还有 Guava Cache、Caffeine 的实现。它们都是成熟的 Local Cache,如果不想使用 DefaultMemoryImpl ,完全可以使用 extra 模块成熟的替代方案。

2.3 Persistence

Persistence 的接口跟 Memory 很类似:

  1. package com.safframework.rxcache.persistence;

  2. import com.safframework.rxcache.domain.Record;

  3. import java.lang.reflect.Type;

  4. import java.util.List;

  5. /**

  6. * Created by tony on 2018/9/28.

  7. */

  8. public interface Persistence {

  9.    <T> Record<T> retrieve(String key, Type type);

  10.    <T> void save(String key, T value);

  11.    <T> void save(String key, T value, long expireTime);

  12.    List<String> allKeys();

  13.    boolean containsKey(String key);

  14.    void evict(String key);

  15.    void evictAll();

  16. }

由于,考虑到持久层可能包括 Disk、DB。于是单独抽象了一个 Disk 接口继承 Persistence。

在 Disk 的实现类 DiskImpl 中,它的构造方法注入了 Converter 接口:

  1. public class DiskImpl implements Disk {

  2.    private File cacheDirectory;

  3.    private Converter converter;

  4.    public DiskImpl(File cacheDirectory,Converter converter) {

  5.        this.cacheDirectory = cacheDirectory;

  6.        this.converter = converter;

  7.    }

  8.    ......

  9. }

Converter 接口用于对象储存到文件的序列化和反序列化,目前支持 Gson 和 FastJSON。

Converter 的抽象实现类 AbstractConverter 的构造方法注入了 Encryptor 接口:

  1. public abstract class AbstractConverter implements Converter {

  2.    private Encryptor encryptor;

  3.    public AbstractConverter() {

  4.    }

  5.    public AbstractConverter(Encryptor encryptor) {

  6.        this.encryptor = encryptor;

  7.    }

  8.    ......

  9. }

Encryptor 接口用于将存储到 Disk 上的数据进行加密和解密,目前 RxCache(https://github.com/fengzhizi715/RxCache) 支持 AES128 和 DES 两种加密方式。不使用 Encryptor 接口,则存储到 Disk 上的数据是明文,也就是一串json字符串。

三. 支持 Java

在 example 模块下,包括了一些常见 Java 使用的例子。

例如,最简单的使用:

  1. import com.safframework.rxcache.RxCache;

  2. import com.safframework.rxcache.domain.Record;

  3. import domain.User;

  4. import io.reactivex.Observable;

  5. import io.reactivex.functions.Consumer;

  6. /**

  7. * Created by tony on 2018/9/29.

  8. */

  9. public class Test {

  10.    public static void main(String[] args) {

  11.        RxCache.config(new RxCache.Builder());

  12.        RxCache rxCache = RxCache.getRxCache();

  13.        User u = new User();

  14.        u.name = "tony";

  15.        u.password = "123456";

  16.        rxCache.save("test",u);

  17.        Observable<Record<User>> observable = rxCache.load2Observable("test", User.class);

  18.        observable.subscribe(new Consumer<Record<User>>() {

  19.            @Override

  20.            public void accept(Record<User> record) throws Exception {

  21.                User user = record.getData();

  22.                System.out.println(user.name);

  23.                System.out.println(user.password);

  24.            }

  25.        });

  26.    }

  27. }

带 ExpireTime 的缓存测试:

  1. import com.safframework.rxcache.RxCache;

  2. import com.safframework.rxcache.domain.Record;

  3. import domain.User;

  4. /**

  5. * Created by tony on 2018/10/5.

  6. */

  7. public class TestWithExpireTime {

  8.    public static void main(String[] args) {

  9.        RxCache.config(new RxCache.Builder());

  10.        RxCache rxCache = RxCache.getRxCache();

  11.        User u = new User();

  12.        u.name = "tony";

  13.        u.password = "123456";

  14.        rxCache.save("test",u,2000);

  15.        try {

  16.            Thread.sleep(2500);

  17.        } catch (InterruptedException e) {

  18.            e.printStackTrace();

  19.        }

  20.        Record<User> record = rxCache.get("test", User.class);

  21.        if (record==null) {

  22.            System.out.println("record is null");

  23.        }

  24.    }

  25. }

跟 Spring 整合并且 Memory 的实现使用 GuavaCacheImpl:

  1. import com.safframework.rxcache.RxCache;

  2. import com.safframework.rxcache.extra.memory.GuavaCacheImpl;

  3. import com.safframework.rxcache.memory.Memory;

  4. import org.springframework.beans.factory.annotation.Configurable;

  5. import org.springframework.context.annotation.Bean;

  6. /**

  7. * Created by tony on 2018/10/5.

  8. */

  9. @Configurable

  10. public class ConfigWithGuava {

  11.    @Bean

  12.    public Memory guavaCache(){

  13.        return new GuavaCacheImpl(100);

  14.    }

  15.    @Bean

  16.    public RxCache.Builder rxCacheBuilder(){

  17.        return new RxCache.Builder().memory(guavaCache());

  18.    }

  19.    @Bean

  20.    public RxCache rxCache() {

  21.        RxCache.config(rxCacheBuilder());

  22.        return RxCache.getRxCache();

  23.    }

  24. }

测试一下刚才的整合:

  1. import com.safframework.rxcache.RxCache;

  2. import com.safframework.rxcache.domain.Record;

  3. import domain.User;

  4. import io.reactivex.Observable;

  5. import io.reactivex.functions.Consumer;

  6. import org.springframework.context.ApplicationContext;

  7. import org.springframework.context.annotation.AnnotationConfigApplicationContext;

  8. /**

  9. * Created by tony on 2018/10/5.

  10. */

  11. public class TestWithGuava {

  12.    public static void main(String[] args) {

  13.        ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithGuava.class);

  14.        RxCache rxCache = ctx.getBean(RxCache.class);

  15.        User u = new User();

  16.        u.name = "tony";

  17.        u.password = "123456";

  18.        rxCache.save("test",u);

  19.        Observable<Record<User>> observable = rxCache.load2Observable("test", User.class);

  20.        observable.subscribe(new Consumer<Record<User>>() {

  21.            @Override

  22.            public void accept(Record<User> record) throws Exception {

  23.                User user = record.getData();

  24.                System.out.println(user.name);

  25.                System.out.println(user.password);

  26.            }

  27.        });

  28.    }

  29. }

四. 支持 Android

为了更好地支持 Android,我还单独创建了一个项目 RxCache4a: https://github.com/fengzhizi715/RxCache4a

它包含了一个基于 LruCache 的 Memory 实现,以及一个基于 MMKV(腾讯开源的key -value存储框架) 的 Persistence 实现。

我们目前 App 采用了如下的 MVVM 架构来传输数据:

640?wx_fmt=png


未来,希望能够通过 RxCache 来整合 Repository 这一层。

五. 总结

目前,RxCache(https://github.com/fengzhizi715/RxCache) 完成了大体的框架,初步可用,接下来打算增加一些 

Annotation,方便其使用。


关注【Java与Android技术栈】

更多精彩内容请关注扫码

640?wx_fmt=jpeg


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值