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

Ümi*_*mit 6 java gwt html5 caching local-storage

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

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

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

public interface DataSource {
    public void fetchStatsData(Stat stat,FetchStatsDataCallback callback);
    public void fetchStatsList(FetchStatsListCallback callback);
}
Run Code Online (Sandbox Code Playgroud)

Stat班是一个简单的JavaScript覆盖类(JavaScriptObject)与一些干将(的getName()等),我有一个正常的非超高速缓存实现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<Stat> stats,final FetchStatsDataCallback callback) {
        String url = getStatUrl(stats);
        //create RequestBuilderRquest, retrieve response and parse JSON
        callback.onFetchStats(dataTable); //dataTable is of type DataTable
    }
}
Run Code Online (Sandbox Code Playgroud)

我遗漏了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();
}
Run Code Online (Sandbox Code Playgroud)

我添加了一个新类RequestBuilderCacheDataSource,它扩展了它RequestBuilderDataSourceCache在其构造函数中获取了一个实例.

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);
                }
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上新类将查找其中的值Cache,如果未找到它将调用父类中的fetch函数并拦截回调以将其放入缓存中,然后调用实际的回调.
因此,为了支持HTML5 localstorage和普通的JS HashMap存储,我创建了两个我的Cache接口实现:

JS HashMap存储:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Col*_*rth 6

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

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

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

public <T> void fetch(Class<T> type, List<Stat> stats, Callback<T, Throwable> callback);
Run Code Online (Sandbox Code Playgroud)

那说,是什么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<DataTable> getTables();
    void setTables(List<DataTable> tables);
    int getRemaining();//useful for not sending all if you have too much?
}
Run Code Online (Sandbox Code Playgroud)

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

interface DataABF extends AutoBeanFactory {
    AutoBean<DataTable> dataTable();
    AutoBean<Stat> stat();
    AutoBean<TableCollection> tableCollection();
}
Run Code Online (Sandbox Code Playgroud)

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

public class AutoBeanSerializer {
    private final AutoBeanFactory factory;
    public AutoBeanSerializer(AutoBeanFactory factory) {
        this.factory = factory;
    }

    public String <T> encodeData(T data) {
        //first, get the autobean mapped to the data
        //probably throw something if we can't find it
        AutoBean<T> 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> T decodeData(Class<T> dataType, String json) {
        AutoBean<T> bean = AutoBeanCodex.decode(factory, dataType, json);

        //unwrap the bean, and return the actual data
        return bean.as();
    }
}
Run Code Online (Sandbox Code Playgroud)