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());

如果您正在构建多层应用程序并在不同 类 之间传递 CompletionStages,这将特别有用。

完整装饰器实现示例is here