Play 2.5 在异步调用中保留上下文
Play 2.5 preserve context in async calls
在我们的控制器 class 中,我们联系另一项服务以获取一些数据:
Future<JsonNode> futureSite = someClient.getSite(siteId, queryParams);
return FutureConverters.toJava(futureSite).thenApplyAsync((siteJson) -> {
Site site = Json.fromJson(siteJson, Site.class);
try {
return function.apply(site);
} catch (RequestException e) {
return e.result;
}
}).exceptionally(throwable -> {
if(throwable instanceof OurClientException) {
if(((OurClientException) throwable).httpStatusCode == 404) {
return entityNotFound("Site", siteId);
}
}
return null;
});
我们注意到在单元测试中设置的上下文(我们使用 scalatest-play)在我们进行异步调用后丢失并变为 null (FutureConverters.toJava(futureSite).thenApplyAsync((siteJson)
,因为 t 在一个单独的线程上。
这会在我们使用上述函数的控制器代码中导致问题... request() 现在会抛出一个运行时异常,表示没有可用的上下文。
我们如何保存上下文?
您应该将 play.libs.concurrent.HttpExecutionContext 注入您的控制器,然后将当前上下文指定为 CompletionStage#thenApplyAsync(..,..) 的第二个参数。
public class Application extends Controller {
@Inject HttpExecutionContext ec;
public CompletionStage<Result> index() {
someCompletableFuture.supplyAsync(() -> {
// do something with request()
}, ec.current());
}}
P.S。 https://www.playframework.com/documentation/2.5.x/JavaAsync#Using-CompletionStage-inside-an-Action
我除了 Nick 的 V 回答。
如果您正在使用 Play Java API 构建非阻塞应用程序,则每次需要时注入 HttpExecutionContext
并传递 ec.current())
可能会变得非常麻烦在 CompletionStage
.
上调用方法
为了让生活更轻松,您可以使用装饰器,它会保留调用之间的上下文。
public class ContextPreservingCompletionStage<T> implements CompletionStage<T> {
private HttpExecutionContext context;
private CompletionStage<T> delegate;
public ContextPreservingCompletionStage(CompletionStage<T> delegate,
HttpExecutionContext context) {
this.delegate = delegate;
this.context = context;
}
...
}
因此您只需要传递一次上下文:
return new ContextPreservingCompletionStage<>(someCompletableFuture, context)
.thenCompose(something -> {...});
.thenApply(something -> {...});
而不是
return someCompletableFuture.thenComposeAsync(something -> {...}, context.current())
.thenApplyAsync(something -> {...}, context.current());
如果您正在构建多层应用程序并在不同 类 之间传递 CompletionStage
s,这将特别有用。
完整装饰器实现示例is here。
在我们的控制器 class 中,我们联系另一项服务以获取一些数据:
Future<JsonNode> futureSite = someClient.getSite(siteId, queryParams);
return FutureConverters.toJava(futureSite).thenApplyAsync((siteJson) -> {
Site site = Json.fromJson(siteJson, Site.class);
try {
return function.apply(site);
} catch (RequestException e) {
return e.result;
}
}).exceptionally(throwable -> {
if(throwable instanceof OurClientException) {
if(((OurClientException) throwable).httpStatusCode == 404) {
return entityNotFound("Site", siteId);
}
}
return null;
});
我们注意到在单元测试中设置的上下文(我们使用 scalatest-play)在我们进行异步调用后丢失并变为 null (FutureConverters.toJava(futureSite).thenApplyAsync((siteJson)
,因为 t 在一个单独的线程上。
这会在我们使用上述函数的控制器代码中导致问题... request() 现在会抛出一个运行时异常,表示没有可用的上下文。
我们如何保存上下文?
您应该将 play.libs.concurrent.HttpExecutionContext 注入您的控制器,然后将当前上下文指定为 CompletionStage#thenApplyAsync(..,..) 的第二个参数。
public class Application extends Controller {
@Inject HttpExecutionContext ec;
public CompletionStage<Result> index() {
someCompletableFuture.supplyAsync(() -> {
// do something with request()
}, ec.current());
}}
P.S。 https://www.playframework.com/documentation/2.5.x/JavaAsync#Using-CompletionStage-inside-an-Action
我除了 Nick 的 V 回答。
如果您正在使用 Play Java API 构建非阻塞应用程序,则每次需要时注入 HttpExecutionContext
并传递 ec.current())
可能会变得非常麻烦在 CompletionStage
.
为了让生活更轻松,您可以使用装饰器,它会保留调用之间的上下文。
public class ContextPreservingCompletionStage<T> implements CompletionStage<T> {
private HttpExecutionContext context;
private CompletionStage<T> delegate;
public ContextPreservingCompletionStage(CompletionStage<T> delegate,
HttpExecutionContext context) {
this.delegate = delegate;
this.context = context;
}
...
}
因此您只需要传递一次上下文:
return new ContextPreservingCompletionStage<>(someCompletableFuture, context)
.thenCompose(something -> {...});
.thenApply(something -> {...});
而不是
return someCompletableFuture.thenComposeAsync(something -> {...}, context.current())
.thenApplyAsync(something -> {...}, context.current());
如果您正在构建多层应用程序并在不同 类 之间传递 CompletionStage
s,这将特别有用。
完整装饰器实现示例is here。