我正在用 Java 编写一个 Play2 应用程序服务方法,它应该执行以下操作。异步调用方法A,如果失败,异步调用方法B。
为了说明假设服务调用的后端的这个接口(interface):
public interface MyBackend {
CompletionStage<Object> tryWrite(Object foo);
CompletionStage<Object> tryCleanup(Object foo);
}
所以在我的服务方法中,我想返回一个可以完成这些的 Future :
(注意:当然 tryWrite() 可以自己做任何清理,这是一个简单的例子来说明问题)
像这样调用后端的服务的实现对我来说似乎很难,因为 CompletionStage.exceptionally() 方法不允许组合。
版本 1:
public class MyServiceImpl {
public CompletionStage<Object> tryWriteWithCleanup(Object foo) {
CompletionStage<Object> writeFuture = myBackend.tryWrite(foo)
.exceptionally((throwable) -> {
CompletionStage<Object> cleanupFuture = myBackend.tryCleanup(foo);
throw new RuntimeException(throwable);
});
return writeFuture;
}
}
因此版本 1 以非阻塞方式调用 tryCleanup(foo),但 tryWriteWithCleanup() 返回的 CompletionStage 不会等待 cleanupFuture 完成。如何更改此代码以从同样等待 cleanupFuture 完成的服务返回 future ?
版本 2:
public class MyServiceImpl {
public CompletionStage<Object> tryWriteWithCleanup(Object foo) {
final AtomicReference<Throwable> saveException = new AtomicReference<>();
CompletionStage<Object> writeFuture = myBackend
.tryWrite(foo)
.exceptionally(t -> {
saveException.set(t);
// continue with cleanup
return null;
})
.thenCompose((nil) -> {
// if no cleanup necessary, return
if (saveException.get() == null) {
return CompletableFuture.completedFuture(null);
}
return CompletionStage<Object> cleanupFuture = myBackend.tryCleanup(foo)
.exceptionally(cleanupError -> {
// log error
return null;
})
.thenRun(() -> {
throw saveException.get();
});
});
return writeFuture;
}
}
Version2 使用外部 AtomicReference 来存储失败,并在另一个 thenCompose() 块中进行异步第二次调用,如果有失败。
我所有其他尝试这样做最终都非常笨拙,我不想将它们粘贴在这里。
最佳答案
不幸的是 CompletionStage
/CompletableFuture
不提供带有组合的异常处理 API。
您可以通过依赖 handle()
和返回 BiFunction
的 CompletionStage
来解决此问题。这将为您提供嵌套阶段( CompletionStage<CompletionStage<Object>>
),您可以使用 compose(identity())
“取消嵌套”:
public CompletionStage<Object> tryWriteWithCleanup(Object foo) {
return myBackend.tryWrite(foo)
.handle((r, e) -> {
if (e != null) {
return myBackend.tryCleanup(foo)
.handle((r2, e2) -> {
// Make sure we always return the original exception
// but keep track of new exception if any,
// as if run in a finally block
if (e2 != null) {
e.addSuppressed(e2);
}
// wrapping in CompletionException behaves as if
// we threw the original exception
throw new CompletionException(e);
});
}
return CompletableFuture.completedFuture(r);
})
.thenCompose(Function.identity());
}
关于java - 如何在 CompletionStage.exceptionally 中链接非阻塞操作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44534386/