#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);
}