使用HTML5 localStorage在GWT app / widget中缓存

我正在尝试为我的一个GWT小部件合并一个数据缓存。
我有一个数据源接口/类,它通过RequestBuilder和JSON从我的后端检索一些数据。 因为我多次显示窗口小部件,所以我只想检索一次数据。

所以我尝试使用app缓存。 天真的方法是在单个对象中使用HashMap来存储数据。 但是,如果支持,我还想使用HTML5的localStorage / sessionStorage。
HTML5 localStorage仅支持String值。 所以我必须将我的对象转换为JSON并存储为字符串。 但不知何故,我无法想出一个干净利落的方法。 这是我到目前为止所拥有的。

我定义了一个具有两个函数的接口: fetchStatsList()获取可以在窗口小部件中显示的统计信息列表, fetchStatsData()获取实际数据。

 public interface DataSource { public void fetchStatsData(Stat stat,FetchStatsDataCallback callback); public void fetchStatsList(FetchStatsListCallback callback); } 

Stat类是一个简单的Javascript Overlay类( JavaScriptObject ),带有一些getter(getName()等)我有一个普通的不可RequestBuilderDataSource的我的DataSource的RequestBuilderDataSource ,如下所示:

 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 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)value); } else { super.fetchStatsList(stats,new FetchStatsListCallback() { @Override public void onFetchStatsList(Liststats) { cache.put("list",stats); callback.onFetchStatsList(stats); } }); super.fetchStatsList(callback); } } @Override public void fetchStatsData(List 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查找该值,如果未找到它,它将调用父类中的fetch函数并拦截回调以将其放入缓存中,然后调用实际的回调。
因此,为了支持HTML5 localstorage和普通的JS HashMap存储,我创建了两个Cache接口的实现:

JS HashMap存储

 public class DefaultcacheImpl implements Cache { private HashMap map; public DefaultCacheImpl() { this.map = new HashMap(); } @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 jsonStats = (JsArray)jsonData; List stats = new ArrayList(); 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; } } 

post有点长,但希望澄清我试图达到的目的。 它归结为两个问题:

  1. 关于此方法的设计/体系结构的反馈(例如,为缓存函数子类化RequestBilderDataSource等)。 这可以改进(这可能与一般设计相比,特别是GWT)。
  2. 使用DefaultCacheImpl ,可以很容易地存储和检索任何任意对象。 我如何使用localStorage实现相同的function,我必须转换并解析JSON? 我正在使用DataTable ,它需要调用DataTable.create(JavaScriptObject jso)函数才能工作。 如果没有很多if / else和检查实例,我怎么能解决这个问题呢?

我的第一个想法:使它成为两层缓存,而不是两个不同的缓存。 从内存映射开始,因此不需要序列化/反序列化来读取给定的对象,因此在一个地方更改对象会改变它。 然后依靠本地存储来保存下一页加载的数据,从而避免从服务器中提取数据。

我倾向于说跳过会话存储,因为这不会持续很长时间,但它确实有它的好处。

为了存储/读取数据,我鼓励检查AutoBeans而不是使用JSO。 这样你就可以支持任何类型的数据(可以存储为autobean)并且可以将Class param传递给fetcher以指定你将从服务器/缓存中读取什么类型的数据,并将json解码为bean以同样的方式。 作为一个额外的好处,autopaans更容易定义 – 不需要JSNI。 方法可能看起来像这样(请注意,在DataSource及其impl中,签名是不同的)。

 public  void fetch(Class type, List stats, Callback callback); 

那说什么是DataTable.create ? 如果它已经是JSO,那么你可以像在阅读RequestBuilder数据时那样(可能)正常地转换为DataTable。

我还建议不要直接从服务器返回JSON数组,而是将其包装在对象中,这是保护用户数据不被其他网站读取的最佳做法。 (好的,在重新阅读问题时,对象也不是很好)。 不是在这里讨论,而是查看JSON安全最佳实践?

所以,所有这些说,首先定义数据(不确定这些数据是如何工作的,所以只是按照我的方式进行补充)

 public interface DataTable { String getTableName(); void setTableName(String tableName); } public interface Stat {// not really clear on what this is supposed to offer String getKey(); void setKey(String key); String getValue(); String setValue(String value); } public interface TableCollection { List getTables(); void setTables(List tables); int getRemaining();//useful for not sending all if you have too much? } 

对于自动转发,我们定义了一个工厂,当给定一个Class实例和一些数据时,它可以创建我们的任何数据。 这些方法中的每一个都可以用作一种构造函数,以在客户端上创建新实例,并且可以将工厂传递给AutoBeanCodex以解码数据。

 interface DataABF extends AutoBeanFactory { AutoBean dataTable(); AutoBean stat(); AutoBean tableCollection(); } 

将String <=> Object的所有工作委托给AutoBeanCodex,但您可能需要一些简单的包装器,以便从html5缓存和RequestBuilder结果中轻松调用。 这里的简单例子:

 public class AutoBeanSerializer { private final AutoBeanFactory factory; public AutoBeanSerializer(AutoBeanFactory factory) { this.factory = factory; } public String  encodeData(T data) { //first, get the autobean mapped to the data //probably throw something if we can't find it AutoBean autoBean = AutoBeanUtils.getAutoBean(data); //then, encode it //no factory or type needed here since the AutoBean has those details return AutoBeanCodex.encode(autoBean); } public  T decodeData(Class dataType, String json) { AutoBean bean = AutoBeanCodex.decode(factory, dataType, json); //unwrap the bean, and return the actual data return bean.as(); } }