【问题标题】:How to cache server results in GWT with guava?如何使用番石榴在 GWT 中缓存服务器结果?
【发布时间】:2016-01-18 09:23:57
【问题描述】:

在我的 GWT 应用程序中,我经常多次引用相同的服务器结果。我也不知道先执行哪个代码。因此,我想对我的异步(客户端)结果进行缓存。

我想使用现有的缓存库;我正在考虑 guava-gwt

我发现了这个 Guava 同步缓存的例子(guava's documentation):

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

这就是我尝试异步使用 Guava 缓存的方式(我不知道如何使这项工作发挥作用):

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {

               // I want to do something asynchronous here, I cannot use Thread.sleep in the browser/JavaScript environment.
               service.createExpensiveGraph(key, new AsyncCallback<Graph>() {

                 public void onFailure(Throwable caught) {
                   // how to tell the cache about the failure???
                 }

                 public void onSuccess(Graph result) {
                   // how to fill the cache with that result???
                 }
               });

               return // I cannot provide any result yet. What can I return???
             }
           });

GWT 缺少默认 JRE 中的许多类(尤其是在线程和并发方面)。

如何使用 guava-gwt 缓存异步结果?

【问题讨论】:

    标签: java caching gwt guava


    【解决方案1】:

    据我了解,您想要实现的不仅是异步缓存,而且是惰性缓存,并且创建一个 GWT 并不是最好的地方,因为在使用客户端异步执行实现 GWT 应用程序时存在一个大问题,因为 GWT 缺少 Futures 和/或 Rx 组件的客户端实现(仍然有一些 RxJava 用于 GWT 的实现)。因此,在通常的 java 中,您想要创建的内容可以通过以下方式实现:

    LoadingCache<String, Future<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, Future<String>>() {
        public Future<String> load(String key) {
            ExecutorService service = Executors.newSingleThreadExecutor();
            return  service.submit(()->service.createExpensiveGraph(key));
        }
    });
    Future<String> value = graphs.get("Some Key");
    if(value.isDone()){
        // This will block the execution until data is loaded 
        String success = value.get();           
    }
    

    但由于 GWT 没有 Futures 的实现,您需要创建一个,就像

    public class FutureResult<T> implements AsyncCallback<T> {
        private enum State {
            SUCCEEDED, FAILED, INCOMPLETE;
        }
    
        private State state = State.INCOMPLETE;
        private LinkedHashSet<AsyncCallback<T>> listeners = new LinkedHashSet<AsyncCallback<T>>();
        private T value;
        private Throwable error;
    
        public T get() {
            switch (state) {
            case INCOMPLETE:
                // Do not block browser so just throw ex
                throw new IllegalStateException("The server response did not yet recieved.");
            case FAILED: {
                throw new IllegalStateException(error);
            }
            case SUCCEEDED:
                return value;
            }
            throw new IllegalStateException("Something very unclear");
        }
    
        public void addCallback(AsyncCallback<T> callback) {
           if (callback == null) return;
           listeners.add(callback);
        }
    
        public boolean isDone() {
            return state == State.SUCCEEDED;
        }
    
        public void onFailure(Throwable caught) {
            state = State.FAILED;
            error = caught;
            for (AsyncCallback<T> callback : listeners) {
                callback.onFailure(caught);
            }
        }
    
        public void onSuccess(T result) {
            this.value = result;
            state = State.SUCCEEDED;
            for (AsyncCallback<T> callback : listeners) {
                callback.onSuccess(value);
            }
        }
    
    }
    

    你的实现将变成:

        LoadingCache<String, FutureResult<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, FutureResult<String>>() {
            public FutureResult<String> load(String key) {
                FutureResult<String> result = new FutureResult<String>();
                return service.createExpensiveGraph(key, result);
            }
        });
    
        FutureResult<String> value = graphs.get("Some Key");
    
        // add a custom handler
        value.addCallback(new AsyncCallback<String>() {
            public void onSuccess(String result) {
                // do something
            }
            public void onFailure(Throwable caught) {
                // do something             
            }
        });
        // or see if it is already loaded / do not wait 
        if (value.isDone()) {
            String success = value.get();
        }
    

    使用FutureResult 时,您不仅会缓存执行,还会获得某种惰性,因此您可以在数据加载到缓存时显示一些loading screen

    【讨论】:

    • 您想要的并不难实现,您只需将成功回调处理程序添加到FutureResult 即可。我将更新一个源代码示例以反映我的想法。但是你应该明白,回调将在第一次加载时被调用一次(这将被缓存)。所以回调后result.isDone 将是真的。
    • 请查看 CompletableFuture - 它的行为更像 Promise,但如果有人使用任何阻塞的 Future 方法,您需要抛出异常。
    • @ColinAlworth GWT 支持CompletableFuture 吗?
    • 不,我建议它作为实现而不是 Future 的替代方案。至少有其他人表示有兴趣帮助在 GWT 中实现此功能,以便将来所有用户都拥有此功能,但我还没有看到任何补丁。
    • 感谢您更新您的答案!一个小提示:我认为return service.createExpensiveGraph(key, result); 必须是service.createExpensiveGraph(key, result); return result;
    【解决方案2】:

    如果你只需要缓存异步调用结果,你可以去一个 非加载缓存,而不是加载缓存

    在这种情况下,您需要使用 put、getIfPresent 方法来存储和从缓存中检索记录。

    String v = cache.getIfPresent("one");
    // returns null
    cache.put("one", "1");
    v = cache.getIfPresent("one");
    // returns "1"
    

    或者,可以在缓存未命中时从 Callable 加载新值

    String v = cache.get(key,
        new Callable<String>() {
            public String call() {
            return key.toLowerCase();
        }
    });
    

    更多参考:https://guava-libraries.googlecode.com/files/JavaCachingwithGuava.pdf

    【讨论】:

    • 这不是我要找的。第一个示例将仅帮助按键存储值,但不提供任何加载数据的帮助。第二个示例将只能处理同步加载。
    • 我的想法是,在异步回调的onSuccess 中获得结果后,您可以将结果放入缓存中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多