为GWT RPC异步调用启用缓存

时间:2012-07-30 02:14:57

标签: gwt caching local-storage

我正在考虑引入某种缓存机制(如HTML5本地存储)以尽可能避免频繁的RPC调用。我想得到关于如何在下面的代码中引入缓存的反馈,而不会改变很多架构(比如使用gwt-dispatch)。

void getData() {

    /* Loading indicator code skipped */

    /* Below is a gwt-maven plugin generated singleton for SomeServiceAsync */
    SomeServiceAsync.Util.getInstance().getDataBySearchCriteria(searchCriteria, new AsyncCallback<List<MyData>>() {

        public void onFailure(Throwable caught) {
            /* Loading indicator code skipped */
            Window.alert("Problem : " + caught.getMessage());
        }

        public void onSuccess(List<MyData> dataList) {
            /* Loading indicator code skipped */
        }
    });
}

我能想到解决这个问题的一种方法是使用自定义MyAsyncCallback类来定义onSuccess / onFailure方法然后执行类似的操作 -

void getData() {

    AsyncCallback<List<MyData>> callback = new MyAsyncCallback<List<MyData>>;

    // Check if data is present in cache
    if(cacheIsPresent)
        callback.onSuccess(dataRetrievedFromCache);
    else
    //  Call RPC and same as above and of course, update cache wherever appropriate
}

除此之外,我还有一个问题。对于流行的浏览器,LocalStorage可用的最大存储空间大小是多少?浏览器如何管理不同应用程序/ URL的LocalStorage?任何指针将不胜感激。

3 个答案:

答案 0 :(得分:2)

我建议添加一个处理缓存的delegate class。委托类可能如下所示:

public class Delegate {

  private static SomeServiceAsync service = SomeServiceAsync.Util.getInstance();

  private List<MyData> data;

  public static void getData(Callback callback) {
    if (date != null) {
      callback.onSuccess(data);
    } else {
      service.getData(new Callback() {
        public onSuccess(List<MyData> result) {
          data = result;
          callback.onSuccess(result);
      });
    }
  }

}

当然这是一个粗略的样本,您必须优化代码以使其可靠。

答案 1 :(得分:1)

我确实花了很长时间才决定使用哈希映射来缓存结果。

我的策略不是使用单例散列图,而是使用存储静态缓存实例的单例公共对象类。我没有看到加载具有过多hashtree分支级别的单个hashmap的原因。

减少散列分辨率

如果我知道我正在处理的对象是Employee,Address,Project,我会创建三个静态哈希

final static private Map<Long, Employee> employeeCache =
  new HashMap<Long, Employee>();
final static private Map<Long, Address> addressCache =
 new HashMap<Long, Address>();
final static private Map<String name, Project> projectCache =
 new HashMap<String name, Project>();

public static void putEmployee(Long id, Employee emp){
  employeeCache.put(id, emp);
}
public static Employee getEmployee(Long id){
  return employeeCache.get(id);
}

public static void putEmployee(Long id, Address addr){
  addressCache.put(id, addr);
}
public static Address getEmployee(Long id){
  return addressCache.get(id);
}

public static void putProject(String name, Address addr){
  projectCache.put(name, addr);
}
public static Address getProject(String name){
  return projectCache.get(name);
}

将所有内容放在一张地图上会很毛茸茸。高效访问和存储数据的原则是 - 您确定的有关数据的信息越多,您就越应该利用您拥有的信息来隔离数据。它会降低访问数据所需的散列分辨率级别。更不用说需要完成的所有风险和不确定类型的铸造。

如果可以,请避免散列

如果您知道您总是拥有CurrentEmployee和NextEmployee的单个值, 避免将它们存储在Employee的哈希中。只需创建静态实例

Employee CurrentEmployee, NextEmployee;

这样可以避免任何哈希解决方案。

避免污染全局命名空间

如果可能的话,将它们保留为类实例而不是静态实例,以避免污染全局命名空间。

为什么要避免污染全局命名空间?因为全局命名空间混淆,不止一个类会无意中使用相同的名称导致无数错误。

保持缓存最接近预期或使用的位置

如果可能,如果缓存主要用于某个类,请将缓存保留为该类中的类实例。并为另一个类从该缓存中获取数据所需的任何罕见实例提供eventbus事件。

这样你就会有一个可预期的模式

ZZZManager.getZZZ(id);

尽可能完成缓存

否则/并通过提供推杆和吸气剂将其私有化。不要让另一个类无意中重新实例化缓存,特别是如果有一天你的类成为通用实用程序库。此外,putter和getter还有机会通过直接使用缓存无法处理的键或值来缓存缓存或将应用程序推送到Exception中来验证请求以避免请求消除缓存。

将这些原则转换为Javascript本地存储

GWT页面说

  

明智地使用命名约定有助于处理存储数据。例如,在名为MyWebApp的Web应用程序中,与名为Stock的UI表中的行关联的键值数据可能具有以MyWebApp.Stock为前缀的键名称。

因此,在您的课程中使用相当粗略的代码补充HashMap,

public class EmployeePresenter {
  Storage empStore = Storage.getLocalStorageIfSupported();
  HashMap<Long, Employee> employeeCache;

  public EmployeePresenter(){
    if (empStore==null) {
      employeeCache = new HashMap<Employee>();
    }
  }

  private String getPrefix(){
    return this.getClass()+".Employee";
    //return this.getClass().getCanonicalName()+".Employee";
  }

  public Employee putEmployee(Long id, Employee employee)
    if (empStore==null) {
      stockStore.setItem(getPrefix()+id, jsonEncode(employee));
      return;
    }
    employeeCache.put(id, employee);
  }

  public Employee getEmployee(Long id)
    if (empStore==null) {
      return (Employee) jsonDecode(Employee.class, stockStore.getItem(getPrefix()+id));
    }
    return employeeCache(id);
  }
}

因为localstore只是基于字符串的,我假设您将编写自己的json编码器解码器。另一方面,为什么不在从回调中收到json时直接将json写入商店?

内存限制?

我不能在这个问题上表达专业知识,但我预测哈希映射的答案是浏览器上操作系统限制的最大内存。减去浏览器,插件和javascript等已经消耗的所有内存。

对于HTML5本地存储,GWT页面显示

“LocalStorage:每个浏览器每个应用5MB。根据HTML5规范,用户可以在需要时增加此限制;但是,只有少数浏览器支持此限制。”

“SessionStorage:仅受系统内存限制”

答案 2 :(得分:0)

由于您正在使用gwt-dispath,因此这里的一个简单解决方案是将请求对象的gwt-dispatch Response对象再次作为Map中的键进行缓存。它易于实现和类型不可知。您需要覆盖Request - equals()方法以查看Request是否已在缓存中。如果是,则从缓存返回响应,否则通过调用命中服务器。

IMO - 如果您只需要会话缓存以提高性能,那么LocalStorage就不是必需的。本地存储仅适用于脱机应用。

您可以查看此内容 - http://turbomanage.wordpress.com/2010/07/12/caching-batching-dispatcher-for-gwt-dispatch/