給 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
- [轉]:如何快速構建一個簡單的程式
- 程式碼來構建一個簡單的compilerCompile
- 使用 Python 構建一個簡單的 RESTful APIPythonRESTAPI
- [譯] 使用 Node 和 OAuth 2.0 構建一個簡單的 REST APIOAuthRESTAPI
- 利用 Docker 構建一個簡單的 java 開發編譯環境DockerJava編譯
- 使用Java和Spring WebFlux構建響應式微服務JavaSpringWebUX微服務
- 實現一個簡易的響應式系統
- 使用 Redis 和 Python 構建一個共享單車的應用程式RedisPython
- 一個簡單的構建React元件動畫方案React元件動畫
- 使用 Proxy 構建響應式系統
- 快速構建一個shell型別的簡單應用 quick-shell.js型別UIJS
- 用 Java 構建簡單的規則引擎Java
- 快速開始構建一個簡單專案
- 使用Java和Dapr構建雲原生應用簡介Java
- 【翻譯】構建響應式系統-vueVue
- 用Java構建一個簡單的WebSocket聊天專案之新增HTTP介面排程JavaWebHTTP
- 自己實現一個VUE響應式--VUE響應式原理Vue
- Maven簡單介紹——必要的Java管理與構建MavenJava
- 一個牛人給Java初學者的建議Java
- [譯] 用 Redis 和 Python 構建一個共享單車的 appRedisPythonAPP
- js和CSS3媒體查詢製作簡單的響應式導航選單JSCSSS3
- 用Keras框架構建一個簡單的卷積神經網路!Keras框架架構卷積神經網路
- [譯] 簡單的響應式現代 CSS 網格佈局CSS
- [譯] 如何使用 Phaser 3 和 TypeScript 在瀏覽器中構建一個簡單的遊戲TypeScript瀏覽器遊戲
- 利用angular4和nodejs-express構建一個簡單的網站(十)—好友模組AngularNodeJSExpress網站
- 深入剖析Vue原始碼 - 響應式系統構建(中)Vue原始碼
- 深入剖析Vue原始碼 - 響應式系統構建(上)Vue原始碼
- 深入剖析Vue原始碼 - 響應式系統構建(下)Vue原始碼
- 一個簡單的 indexedDB 應用示例Index
- Jib構建你的第一個java映象Java
- 使用 Vue.js 和 Flask 來構建一個單頁的AppVue.jsFlaskAPP
- jQuery簡單實用的響應式固定側邊欄外掛jQuery
- 使用docker-compsoe構建一個簡單nginx+php環境DockerNginxPHP
- 響應式程式設計在Android 中的一些探索程式設計Android
- 如何構建一個WEB同構應用Web
- 純CSS實現的響應式個人簡歷頁面CSS