Как кэшировать результаты сервера в GWT с помощью guava?

В моем приложении GWT я часто ссылаюсь несколько раз на те же результаты сервера. Я также не знаю, какой код выполняется первым. Поэтому я хочу использовать кеширование моих асинхронных (клиентских) результатов.

Я хочу использовать существующую библиотеку кеширования; Я рассматриваю guava-gwt.

Я нашел этот пример кеша Guava с синхроннойдокументации guava):

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 для кеширования асинхронных результатов?

Ответ 1

Поскольку я понял, чего вы хотите достичь, это не просто асинхронный кеш, но и ленивый кеш, и для его создания GWT не является лучшим местом, так как существует большая проблема при реализации приложения GWT с клиентской стороной. Асинхронные исполнения, поскольку GWT не имеет реализации на стороне клиента компонентов Future и/или 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 не имеет реализаций для Future, вам нужно создать его так же, как

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, пока данные загружаются в кеш.

Ответ 2

Если вам просто нужно кэшировать результаты асинхронного вызова, вы можете пойти на Не загружаемый кэш вместо кэша загрузки

В этом случае вам нужно использовать методы put, getIfPresent для хранения и извлечения записей из кеша.

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

В качестве альтернативы новое значение может быть загружено из пропущенных пропусков кэша

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

Дальнейшая ссылка: https://guava-libraries.googlecode.com/files/JavaCachingwithGuava.pdf