Я пытаюсь включить кэш данных для одного из моих виджетов GWT.
У меня есть интерфейс / класс источника данных, который извлекает некоторые данные из моего бэкэнда через RequestBuilder
и JSON.Поскольку я отображаю виджет несколько раз, я хочу получить данные только один раз.
Итак, я попытался зайти с кешем приложений.Наивный подход заключается в использовании HashMap
в одноэлементном объекте для хранения данных.Однако я также хочу использовать HTML5 localStorage / sessionStorage, если поддерживается.
HTML5 localStorage поддерживает только строковые значения.Поэтому я должен преобразовать свой объект в JSON и сохранить в виде строки.Однако, так или иначе, я не могу придумать хороший чистый способ сделать это.вот что у меня есть.
Я определяю интерфейс с двумя функциями: fetchStatsList()
выбирает список статистики, которая может отображаться в виджете, а fetchStatsData()
выбирает фактические данные.
public interface DataSource {
public void fetchStatsData(Stat stat,FetchStatsDataCallback callback);
public void fetchStatsList(FetchStatsListCallback callback);
}
Класс Stat
- это простой класс наложения Javascript (JavaScriptObject
) с некоторыми получателями (getName () и т. Д.). У меня есть обычная не кэшируемая реализация RequestBuilderDataSource
моего DataSource, котораявыглядит следующим образом:
public class RequestBuilderDataSource implements DataSource {
@Override
public void fetchStatsList(final FetchStatsListCallback callback) {
// create RequestBuilderRequest, retrieve response and parse JSON
callback.onFetchStatsList(stats);
}
@Override
public void fetchStatsData(List<Stat> stats,final FetchStatsDataCallback callback) {
String url = getStatUrl(stats);
//create RequestBuilderRquest, retrieve response and parse JSON
callback.onFetchStats(dataTable); //dataTable is of type DataTable
}
}
Я пропустил большую часть кода для RequestBuilder, поскольку это довольно просто.
Это работает из коробки, однако список статистики, а такжеданные извлекаются каждый раз, даже когда данные распределяются между каждым экземпляром виджета.
Для поддержки кэширования я добавляю интерфейс Cache
и две реализации Cache (одну для HTML5 localStorage и одну для HashMap):
public interface Cache {
void put(Object key, Object value);
Object get(Object key);
void remove(Object key);
void clear();
}
Я добавляю новый класс RequestBuilderCacheDataSource
, который расширяетсяRequestBuilderDataSource
и принимает экземпляр Cache
в своем конструкторе.
public class RequestBuilderCacheDataSource extends RequestBuilderDataSource {
private final Cache cache;
publlic RequestBuilderCacheDataSource(final Cache cache) {
this.cache = cache;
}
@Override
public void fetchStatsList(final FetchStatsListCallback callback) {
Object value = cache.get("list");
if (value != null) {
callback.fetchStatsList((List<Stat>)value);
}
else {
super.fetchStatsList(stats,new FetchStatsListCallback() {
@Override
public void onFetchStatsList(List<Stat>stats) {
cache.put("list",stats);
callback.onFetchStatsList(stats);
}
});
super.fetchStatsList(callback);
}
}
@Override
public void fetchStatsData(List<Stat> stats,final FetchStatsDataCallback callback) {
String url = getStatUrl(stats);
Object value = cache.get(url);
if (value != null) {
callback.onFetchStatsData((DataTable)value);
}
else {
super.fetchStatsData(stats,new FetchStatsDataCallback() {
@Override
public void onFetchStatsData(DataTable dataTable) {
cache.put(url,dataTable);
callback.onFetchStatsData(dataTable);
}
});
}
}
}
По сути, новый класс будет искать значение в Cache
и, если он не найден, он будет вызывать функцию выборки вродительский класс и перехватить обратный вызов, чтобы поместить его в кеш, а затем вызвать реальный обратный вызов.
Итак, для поддержки как локального хранилища HTML5, так и обычного хранилища JS HashMap, я создал две реализации моего Cache
интерфейса:
JS HashMap storage :
public class DefaultcacheImpl implements Cache {
private HashMap<Object, Object> map;
public DefaultCacheImpl() {
this.map = new HashMap<Object, Object>();
}
@Override
public void put(Object key, Object value) {
if (key == null) {
throw new NullPointerException("key is null");
}
if (value == null) {
throw new NullPointerException("value is null");
}
map.put(key, value);
}
@Override
public Object get(Object key) {
// Check for null as Cache should not store null values / keys
if (key == null) {
throw new NullPointerException("key is null");
}
return map.get(key);
}
@Override
public void remove(Object key) {
map.remove(key);
}
@Override
public void clear() {
map.clear();
}
}
HTML5 localStorage :
public class LocalStorageImpl implements Cache{
public static enum TYPE {LOCAL,SESSION}
private TYPE type;
private Storage cacheStorage = null;
public LocalStorageImpl(TYPE type) throws Exception {
this.type = type;
if (type == TYPE.LOCAL) {
cacheStorage = Storage.getLocalStorageIfSupported();
}
else {
cacheStorage = Storage.getSessionStorageIfSupported();
}
if (cacheStorage == null) {
throw new Exception("LocalStorage not supported");
}
}
@Override
public void put(Object key, Object value) {
//Convert Object (could be any arbitrary object) into JSON
String jsonData = null;
if (value instanceof List) { // in case it is a list of Stat objects
JSONArray array = new JSONArray();
int index = 0;
for (Object val:(List)value) {
array.set(index,new JSONObject((JavaScriptObject)val));
index = index +1;
}
jsonData = array.toString();
}
else // in case it is a DataTable
{
jsonData = new JSONObject((JavaScriptObject) value).toString();
}
cacheStorage.setItem(key.toString(), jsonData);
}
@Override
public Object get(Object key) {
if (key == null) {
throw new NullPointerException("key is null");
}
String jsonDataString = cacheStorage.getItem(key.toString());
if (jsonDataString == null) {
return null;
}
Object data = null;
Object jsonData = JsonUtils.safeEval(jsonDataString);
if (!key.equals("list"))
data = DataTable.create((JavaScriptObject)data);
else if (jsonData instanceof JsArray){
JsArray<GenomeStat> jsonStats = (JsArray<GenomeStat>)jsonData;
List<GenomeStat> stats = new ArrayList<GenomeStat>();
for (int i = 0;i<jsonStats.length();i++) {
stats.add(jsonStats.get(i));
}
data = (Object)stats;
}
return data;
}
@Override
public void remove(Object key) {
cacheStorage.removeItem(key.toString());
}
@Override
public void clear() {
cacheStorage.clear();
}
public TYPE getType() {
return type;
}
}
Сообщение получилось немного длинным, но, надеюсь, прояснилосьчего я пытаюсь достичь.Он сводится к двум вопросам:
- Отзывы о дизайне / архитектуре этого подхода (например, создание подкласса RequestBilderDataSource для функции кэширования и т. Д.).Может ли это быть улучшено (это, вероятно, больше связано с общим дизайном, чем конкретно с GWT).
- С
DefaultCacheImpl
действительно легко хранить и извлекать любые произвольные объекты.Как я могу добиться того же с помощью localStorage, где мне нужно конвертировать и анализировать JSON?Я использую DataTable
, который требует вызова функции DataTable.create(JavaScriptObject jso)
для работы.Как я могу решить эту проблему без многих if / else и экземпляров проверок?