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

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

一. 为何要创建这个库



首先,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,最重要是需要有以下的这些方法:

<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() 方法包含了失效时间的参数expireTime,这对于 Local Cache 是比较重要的一个方法,超过这个时间,这个数据将会失效。


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

<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 时,可以考虑结合这些策略来缓存数据。


以 CacheFirstStrategy 为例:

/**
 * 缓存优先的策略,缓存取不到时取接口的数据。
 * 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 。


image.png

RxCache.png


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();
}


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


在 extra 模块还有 Guava Cache、Caffeine 的实现。它们都是成熟的 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 的实现类 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 的抽象实现类 AbstractConverter 的构造方法注入了 Encryptor 接口:

public abstract class AbstractConverter implements Converter {
    private Encryptor encryptor;
    public AbstractConverter() {
    }
    public AbstractConverter(Encryptor encryptor) {
        this.encryptor = encryptor;
    }
    ......
}


Encryptor 接口用于将存储到 Disk 上的数据进行加密和解密,目前 RxCache 支持 AES128 和 DES 两种加密方式。不使用 Encryptor 接口,则存储到 Disk 上的数据是明文,也就是一串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 的实现使用 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,我还单独创建了一个项目 RxCache4a: https://github.com/fengzhizi715/RxCache4a


它包含了一个基于 LruCache 的 Memory 实现,以及一个基于 MMKV(腾讯开源的key

-value存储框架) 的 Persistence 实现。


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

image.png

MVVM.png


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


五. 总结



目前,RxCache 完成了大体的框架,初步可用,接下来打算增加一些 Annotation,方便其使用。

目录
打赏
0
0
0
0
0
分享
相关文章
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
145 11
JBoltAI 框架完整实操案例 在 Java 生态中快速构建大模型应用全流程实战指南
本案例基于JBoltAI框架,展示如何快速构建Java生态中的大模型应用——智能客服系统。系统面向电商平台,具备自动回答常见问题、意图识别、多轮对话理解及复杂问题转接人工等功能。采用Spring Boot+JBoltAI架构,集成向量数据库与大模型(如文心一言或通义千问)。内容涵盖需求分析、环境搭建、代码实现(知识库管理、核心服务、REST API)、前端界面开发及部署测试全流程,助你高效掌握大模型应用开发。
101 5
酷阿鲸森林农场:使用 Java 构建的去中心化区块链电商系统
酷阿鲸森林农场推出基于Java的轻量级区块链电商系统,解决传统农产品电商信任问题。该系统无需以太坊或服务器,通过自研区块链引擎实现去中心化点对点交易,确保数据不可篡改。每个用户节点运行桌面软件参与数据共识,支持订单上链、链同步与验证。项目具备简单轻量、真实可控等优势,适用于农户合作社及小型有机电商,并可扩展签名认证、NFT凭证等功能,推动农业数字主权与数据可信发展。
酷阿鲸森林农场:使用 Java 构建的去中心化区块链电商系统
【JavaEE】从 0 到 1 掌握 Maven 构建 Java 项目核心技巧 解锁 Java 项目高效管理实用实例
本文从Maven基础概念讲起,涵盖安装配置、核心概念(如POM与依赖管理)及优化技巧。结合Java Web项目实例,演示如何用Maven构建和管理项目,解决常见问题,助你高效掌握这一强大工具,提升Java开发与项目管理能力。适合初学者及进阶开发者学习。资源链接:[点此获取](https://pan.quark.cn/s/14fcf913bae6)。
71 6
|
2月前
|
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
300 8
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
构建高效Java后端与前端交互的定时任务调度系统
通过以上步骤,我们构建了一个高效的Java后端与前端交互的定时任务调度系统。该系统使用Spring Boot作为后端框架,Quartz作为任务调度器,并通过前端界面实现用户交互。此系统可以应用于各种需要定时任务调度的业务场景,如数据同步、报告生成和系统监控等。
132 9
|
6月前
|
使用Java和Spring Data构建数据访问层
本文介绍了如何使用 Java 和 Spring Data 构建数据访问层的完整过程。通过创建实体类、存储库接口、服务类和控制器类,实现了对数据库的基本操作。这种方法不仅简化了数据访问层的开发,还提高了代码的可维护性和可读性。通过合理使用 Spring Data 提供的功能,可以大幅提升开发效率。
145 21
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
1352 54
基于开源框架Spring AI Alibaba快速构建Java应用
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问