給 Java 和 Android 構建一個簡單的響應式Local Ca
一. 為何要建立這個庫
首先,Local Cache 不是類似於 Redis、Couchbase、Memcached 這樣的分散式 Cache。Local Cache 適用於在單機環境下,對訪問頻率高、更新次數少的資料進行存放。因此,Local Cache 不適合存放大量的資料。
Local Cache 特別適合於 App,也適合在 Java 的某些場景下使用。
我們的 App 使用 Retrofit 作為網路框架,並且大量使用 RxJava,因此我考慮建立一個 RxCache 來快取一些必要的資料。
RxCache 地址:
二. 如何構建 RxCache
2.1 RxCache 的基本方法
對於 Local Cache,最重要是需要有以下的這些方法:
T> RecordT> 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);
SetString> getAllKeys();
void remove(String key);
void clear();
其中,有一個 save() 方法包含了失效時間的引數expireTime,這對於 Local Cache 是比較重要的一個方法,超過這個時間,這個資料將會失效。
既然是 RxCache,對於獲取資料肯定需要類似這樣的方法:
T> ObservableRecordT>> load2Observable(final String key, final Type type) ;
T> FlowableRecordT>> load2Flowable(final String key, final Type type);
T> SingleRecordT>> load2Single(final String key, final Type type);
T> MaybeRecordT>> 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> PublisherRecordT>> execute(RxCache rxCache, String key, FlowableT> source, Type type) {
FlowableRecordT>> cache = rxCache.> load2Flowable(key, type);
FlowableRecordT>> remote = source
.map(new FunctionT, RecordT>>() {
@Override
public RecordT> apply(@NonNull T t) throws Exception {
rxCache.save(key, t);
return new Record>(Source.CLOUD, key, t);
}
});
return cache.switchIfEmpty(remote);
}
@Override
public T> MaybeRecordT>> execute(RxCache rxCache, String key, MaybeT> source, Type type) {
MaybeRecordT>> cache = rxCache.> load2Maybe(key, type);
MaybeRecordT>> remote = source
.map(new FunctionT, RecordT>>() {
@Override
public RecordT> apply(@NonNull T t) throws Exception {
rxCache.save(key, t);
return new Record>(Source.CLOUD, key, t);
}
});
return cache.switchIfEmpty(remote);
}
@Override
public T> ObservableRecordT>> execute(RxCache rxCache, String key, ObservableT> source, Type type) {
ObservableRecordT>> cache = rxCache.> load2Observable(key, type);
ObservableRecordT>> remote = source
.map(new FunctionT, RecordT>>() {
@Override
public RecordT> 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> RecordT> getIfPresent(String key);
T> void put(String key, T value);
T> void put(String key, T value, long expireTime);
SetString> 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> RecordT> retrieve(String key, Type type);
T> void save(String key, T value);
T> void save(String key, T value, long expireTime);
ListString> 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 上的資料進行加密和解密,目前 支援 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);
ObservableRecordUser>> observable = rxCache.load2Observable("test", User.class);
observable.subscribe(new ConsumerRecordUser>>() {
@Override
public void accept(RecordUser> 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();
}
RecordUser> 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);
ObservableRecordUser>> observable = rxCache.load2Observable("test", User.class);
observable.subscribe(new ConsumerRecordUser>>() {
@Override
public void accept(RecordUser> record) throws Exception {
User user = record.getData();
System.out.println(user.name);
System.out.println(user.password);
}
});
}
}
四. 支援 Android
為了更好地支援 Android,我還單獨建立了一個專案 RxCache4a:
它包含了一個基於 LruCache 的 Memory 實現,以及一個基於 MMKV(騰訊開源的key
-value儲存框架) 的 Persistence 實現。
我們目前 App 採用瞭如下的 MVVM 架構來傳輸資料:
未來,希望能夠透過 RxCache 來整合 Repository 這一層。
五. 總結
目前, 完成了大體的框架,初步可用,接下來打算增加一些 Annotation,方便其使用。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1762/viewspace-2814791/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 給 Java 和 Android 構建一個簡單的響應式Local CacheJavaAndroid
- 用Java構建一個簡單的WebSocket聊天室JavaWeb
- 使用SlashDB,Go和Vue構建一個簡單的時間表應用程式GoVue
- 一個簡單的構建React元件動畫方案React元件動畫
- [轉]:如何快速構建一個簡單的程式
- [譯] 使用 Node 和 OAuth 2.0 構建一個簡單的 REST APIOAuthRESTAPI
- 實現一個簡易的響應式系統
- 利用 Docker 構建一個簡單的 java 開發編譯環境DockerJava編譯
- 使用 Proxy 構建響應式系統
- 程式碼來構建一個簡單的compilerCompile
- 從零構建一個簡單的 Python 框架Python框架
- 構建一個簡單的react-typescript專案ReactTypeScript
- 使用 Python 構建一個簡單的 RESTful APIPythonRESTAPI
- 使用Java和Spring WebFlux構建響應式微服務JavaSpringWebUX微服務
- 快速構建一個shell型別的簡單應用 quick-shell.js型別UIJS
- 給Android應用開發者的十個建議Android
- 使用media Queries實現一個響應式的選單
- 使用 Redis 和 Python 構建一個共享單車的應用程式RedisPython
- 快速開始構建一個簡單專案
- 【翻譯】構建響應式系統-vueVue
- [譯]使用MVI構建響應式安卓程式安卓
- 用 Java 構建簡單的規則引擎Java
- 使用Java和Dapr構建雲原生應用簡介Java
- jQuery建構函式簡單介紹jQuery函式
- Vue響應式原理以及簡單實現Vue
- 簡單響應式圖表 Chartist.jsJS
- 自己實現一個VUE響應式--VUE響應式原理Vue
- 一個牛人給Java初學者的建議Java
- 用Java構建一個簡單的WebSocket聊天專案之新增HTTP介面排程JavaWebHTTP
- js Date()建構函式簡單介紹JS函式
- javascript建構函式的繼承簡單介紹JavaScript函式繼承
- Java 正規表示式的簡單應用.Java
- 響應式佈局簡單程式碼例項
- 用Akka構建一個簡易的分散式檔案系統分散式
- Maven簡單介紹——必要的Java管理與構建MavenJava
- Java的建構函式Java函式
- Java代理以及在Android中的一些簡單應用JavaAndroid
- [譯] 如何使用 Phaser 3 和 TypeScript 在瀏覽器中構建一個簡單的遊戲TypeScript瀏覽器遊戲