Java 使用HTML5 localStorage在GWT应用程序/小部件中缓存

Java 使用HTML5 localStorage在GWT应用程序/小部件中缓存,java,html,gwt,caching,local-storage,Java,Html,Gwt,Caching,Local Storage,我正在尝试为我的一个GWT小部件合并一个数据缓存。 我有一个数据源接口/类,它通过RequestBuilder和JSON从后端检索一些数据。因为我多次显示小部件,所以我只想检索一次数据 所以我试着用一个应用缓存。最简单的方法是在单例对象中使用HashMap来存储数据。不过,如果支持的话,我还想利用HTML5的本地存储/会话存储。 HTML5 localStorage仅支持字符串值。因此,我必须将我的对象转换为JSON并存储为字符串。然而,不知何故,我不能想出一个干净的方法来做这件事。这是我到目前

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

所以我试着用一个应用缓存。最简单的方法是在单例对象中使用
HashMap
来存储数据。不过,如果支持的话,我还想利用HTML5的本地存储/会话存储。
HTML5 localStorage仅支持字符串值。因此,我必须将我的对象转换为JSON并存储为字符串。然而,不知何故,我不能想出一个干净的方法来做这件事。这是我到目前为止所拥有的

我定义了一个带有两个函数的界面:
fetchStatsList()
获取可在小部件中显示的统计列表,以及
fetchStatsData()
获取实际数据

public interface DataSource {
    public void fetchStatsData(Stat stat,FetchStatsDataCallback callback);
    public void fetchStatsList(FetchStatsListCallback callback);
}
Stat
类是一个简单的Javascript覆盖类(
JavaScriptObject
),带有一些getter(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
    }
}
我添加了一个新类
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);
                }
            });
        }
    }
}
HTML5本地存储

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();
    }
}
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;
    }
}
public类LocalStorageImpl实现缓存{
公共静态枚举类型{LOCAL,SESSION}
私有类型;
私有存储cacheStorage=null;
公共LocalStorageImpl(类型)引发异常{
this.type=type;
if(type==type.LOCAL){
cacheStorage=Storage.getLocalStorageIfSupported();
}
否则{
cacheStorage=Storage.getSessionStorageIfSupported();
}
if(cacheStorage==null){
抛出新异常(“不支持本地存储”);
}
}
@凌驾
公共void put(对象键、对象值){
//将对象(可以是任意对象)转换为JSON
字符串jsonData=null;
if(value instanceof List){//如果是Stat对象列表
JSONArray数组=新的JSONArray();
int指数=0;
for(对象值:(列表)值){
set(索引,新的JSONObject((JavaScriptObject)val));
指数=指数+1;
}
jsonData=array.toString();
}
else//如果是数据表
{
jsonData=新的JSONObject((JavaScriptObject)值).toString();
}
setItem(key.toString(),jsonData);
}
@凌驾
公共对象获取(对象密钥){
if(key==null){
抛出新的NullPointerException(“键为null”);
}
String jsonDataString=cacheStorage.getItem(key.toString());
if(jsonDataString==null){
返回null;
}
对象数据=null;
对象jsonData=JsonUtils.safeEval(jsonDataString);
如果(!key.equals(“列表”))
data=DataTable.create((JavaScriptObject)数据);
else if(JsArray的jsonData实例){
JsArray jsonStats=(JsArray)jsonData;
List stats=new ArrayList();

对于(int i=0;i我的第一个想法是:将其设置为两层缓存,而不是两个不同的缓存。从内存映射开始,这样就不需要序列化/反序列化来读取给定的对象,这样在一个位置更改一个对象就会改变它的所有内容。然后依靠本地存储为下一个页面加载保留数据,避免n用于从服务器下拉数据的eed

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

对于存储/读取数据,我鼓励签出autobean而不是使用JSO并且可以将类param传递到fetcher中,以指定您将从服务器/缓存中读取的数据类型,并以相同的方式将json解码为bean。另外,autobeans更易于定义,不需要JSNI。方法可能类似于此(请注意,在DataSource及其impl中,签名是不同的)

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

interface DataABF extends AutoBeanFactory {
    AutoBean<DataTable> dataTable();
    AutoBean<Stat> stat();
    AutoBean<TableCollection> tableCollection();
}
接口数据ABF扩展了AutoBeanFactory{
自动bean数据表();
自动bean stat();
AutoBean tableCollection();
}
将StringObject的所有工作委托给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();
    }
}
公共类AutoBeanSerializer{
私人汽车制成品厂;
公共AutoBean序列化程序(AutoBean工厂){
这个工厂=工厂;
}
公共字符串编码数据(T数据){
//首先,将自动bean映射到数据
//如果我们找不到,可能会扔东西
AutoBean AutoBean=AutoBeanUtils.getAutoBean(数据);
//然后,对其进行编码
//这里不需要工厂或类型,因为AutoBean有这些详细信息
返回AutoBeanCodex.encode(autoBean);
}
公共解码数据(类数据类型,字符串json){
autobeanbean=AutoBeanCodex.decode(工厂、数据类型、json);
//展开bean,并返回实际数据
返回bean.as();
}
}

感谢您的反馈。
Stat
只是一个简单的bean,它描述了我可以显示的每个数据表。它有(名称、标签、isStackable等)字段。数据表是
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?
}
interface DataABF extends AutoBeanFactory {
    AutoBean<DataTable> dataTable();
    AutoBean<Stat> stat();
    AutoBean<TableCollection> tableCollection();
}
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();
    }
}