在我们的控制器类中,我们联系其他服务以获取一些数据:
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)丢失并在我们进行异步调用(FutureConverters.toJava(futureSite).thenApplyAsync((siteJson)
后变为空,因为t在一个单独的线程上。
这会导致控制器代码中出现问题,我们使用上面的函数... request()现在会抛出一个运行时异常,说明没有可用的上下文。
我们如何保留上下文?
答案 0 :(得分:3)
您应该将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
答案 1 :(得分:0)
我除了尼克的答案。
如果您使用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
,那么这将非常有用。
完整的装饰器实现示例is here。