JAX-RS 2: использование Guava ListenableFuture для асинхронных ресурсов

#java #web-services #rest #asynchronous #jax-rs

#java #веб-сервисы #остальное #асинхронный #jax-rs

Вопрос:

Запись асинхронной конечной точки в JAX-RS 2 обычно выглядит следующим образом:

 @GET
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
    new Thread(new Runnable() {

        @Override
        public void run() {
            String result = veryExpensiveOperation();
            asyncResponse.resume(result);
        }

        private String veryExpensiveOperation() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // ignore
            }
            return "DONE";
        }
    }).start();
}
  

Однако этот метод не очень хорош, поскольку он не является декларативным. Я видел в некоторых приложениях JAX-RS, что можно использовать Google Guava и писать такой код:

 ListeningExecutorService executor = ...;

@GET
public ListenableFuture<String> asyncGet() {
    return executor.submit(new Callable<String>() {
        @Override
        public String call() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // ignore
            }
            return "DONE";
        }
    });
}
  

Однако я не знаю, как настроить реализацию JAX-RS 2 для приема такой конечной точки, и я не понял, как это сделали другие реализации.

Как бы я настроил, например, Джерси для принятия такого рода асинхронного объявления?

Ответ №1:

Возобновите asyncRresponse обратный вызов в будущем:

 @GET
@Produces(MediaType.TEXT_PLAIN)
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
    ListenableFuture<String> future = asyncGet();
    Futures.addCallback(future, new FutureCallback<String>() {

        @Override
        public void onSuccess(String result) {
            asyncResponse.resume(result);
        }

        @Override
        public void onFailure(Throwable exception) {
            asyncResponse.resume(exception);
        }
    });
}
  

Убедитесь, что асинхронные запросы включены в конфигурации вашего сервлета в web.xml :

 <servlet>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    ...
    <async-supported>true</async-supported>
</servlet>
  

и что вы используете servlet 3.0 API.

Комментарии:

1. Технически это правильный ответ, но я бы хотел, чтобы это было автоматически. Код с Futures.addCallback будет выглядеть точно так же в каждом обработчике, поэтому было бы лучше, если бы можно было внедрить поставщика, который делает это автоматически для каждой конечной точки, которая возвращает ListenableFuture

2. @dflemstr Теперь я понял, что вам нужно. К сожалению, я не знаю, как это сделать автоматически.

3. @dflemstr вы когда-нибудь выясняли, как это сделать автоматически? Кроме того, знаете ли вы, как это можно сделать, используя реализацию springs ListenableFuture?

4. @LukaszWiktor: когда я попытался добавить <поддерживаемый async> true</async-supported> в web.xml он показывает следующую ошибку: «cvc-complex-type.2.4.a: Обнаружено недопустимое содержимое, начинающееся с элемента ‘init-param’. Один из ‘{» xmlns.jcp.org/xml/ns/javaee «:run-как , » xmlns.jcp.org/xml/ns/javaee «:security-role-ссылка «, xmlns.jcp.org/xml/ns javaee»:ожидается multipart-config}’.» Как исправить такой случай?

5. @Milson Какая версия сервлета определена в вашем web.xml ? async-supported доступно с версии 3.0

Ответ №2:

Я не знаю способа сделать то, что вы пытаетесь сделать, но более чистым решением было бы создать общий класс обратного вызова:

 class GenericFutureCallback<T> implements ListenableFutureCallback<T> {

        private AsyncResponse asyncResponse;
        private Response.Status status;

        public GenericFutureCallback(AsyncResponse asyncResponse, Response.Status status) {
            this.asyncResponse = asyncResponse;
            this.status = status;
        }

        @Override
        public void onFailure(Throwable ex) {
            asyncResponse.resume(ex);
        }

        @Override
        public void onSuccess(T result) {
            Response response = Response.status(status).entity(result).build();
            asyncResponse.resume(response);
        }

    }
  

Тогда ваш контроллер будет выглядеть так:

 @GET
@Produces(MediaType.TEXT_PLAIN)
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
    ListenableFuture<String> future = asyncGet();
    GenericFutureCallback<String> callback = new GenericFutureCallback<String>(asyncRes, Response.Status.OK);
    Futures.addCallback(future, callback);
}