在异步调用中播放2.5保留上下文

时间:2016-11-17 16:44:49

标签: scala playframework scalatest playframework-2.5

在我们的控制器类中,我们联系其​​他服务以获取一些数据:

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()现在会抛出一个运行时异常,说明没有可用的上下文。

我们如何保留上下文?

2 个答案:

答案 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