ListenableFuture 和 CompletableFuture 中的回調
1. 概述
ListenableFuture
和CompletableFuture
建構在 Java 的Future
介面之上。前者是 Google Guava 函式庫的一部分,而後者是 Java 8 的一部分。
我們知道, Future
介面不提供回呼功能。 ListenableFuture
和CompletableFuture
都填補了這一空白。在本教程中,我們將學習使用它們的回呼機制。
2. 非同步任務中的回調
讓我們定義一個用例,我們需要從遠端伺服器下載映像檔並將映像檔的名稱保留在資料庫中。由於此任務包含透過網路進行的操作並消耗時間,因此它是使用 Java 非同步功能的完美案例。
我們可以建立一個函數,從遠端伺服器下載檔案並附加一個偵聽器,然後在下載完成時將資料推送到資料庫。
讓我們看看如何使用ListenableFuture
和CompletableFuture
來完成此任務。
3. ListenableFuture
中的回調
讓我們先在pom.xml
中加入 Google 的 Guava 庫相依性:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
現在,讓我們模擬一個從遠端 Web 伺服器下載檔案的Future
:
ExecutorService executorService = Executors.newFixedThreadPool(1);
ListeningExecutorService pool = MoreExecutors.listeningDecorator(executorService);
ListenableFuture<String> listenableFuture = pool.submit(downloadFile());
private static Callable<String> downloadFile() {
return () -> {
// Mimicking the downloading of a file by adding a sleep call
Thread.sleep(5000);
return "pic.jpg";
};
}
上面的程式碼建立了一個包裹在MoreExecutors
中的ExecutorService
來建立線程池。在ListenableFutureService
的 commit 方法中,我們傳遞一個Callable<String>
來下載檔案並傳回傳回ListenableFuture
的檔案的名稱。
為了在ListenableFuture
實例上附加回調,Guava 在Future
中提供了一個實用方法:
Futures.addCallback(
listenableFuture,
new FutureCallback<>() {
@Override
public void onSuccess(String fileName) {
// code to push fileName to DB
}
@Override
public void onFailure(Throwable throwable) {
// code to take appropriate action when there is an error
}
},
executorService);
}
因此,在這個回調中,成功和異常場景都會被處理。這種使用回呼的方式是很自然的。
我們也可以透過直接將偵聽器新增至ListenableFuture
來新增偵聽器:
listenableFuture.addListener(
new Runnable() {
@Override
public void run() {
// logic to download file
}
},
executorService
);
此回呼無法存取Future
的結果,因為其輸入是Runnable.
無論Future
是否成功完成,此回呼方法都會執行。
在了解ListenableFuture
中的回呼之後,下一節將探討CompletableFuture
中實現相同目的的方法。
CompletableFuture
中的回調
在CompletableFuture,
有許多方法可以附加回呼。最受歡迎的方法是使用正常或異常執行的連結方法,例如thenApply()
、 thenAccept()
、 thenCompose()
、 exceptionally()
等。
在本節中,我們將了解一個方法whenComplete().
該方法最好的一點是它可以從任何希望它完成的線程中完成。使用上面的文件下載範例,讓我們看看如何使用whenComplete()
:
CompletableFuture<String> completableFuture = new CompletableFuture<>();
Runnable runnable = downloadFile(completableFuture);
completableFuture.whenComplete(
(res, error) -> {
if (error != null) {
// handle the exception scenario
} else if (res != null) {
// send data to DB
}
});
new Thread(runnable).start();
private static Runnable downloadFile(CompletableFuture<String> completableFuture) {
return () -> {
try {
// logic to download to file;
} catch (InterruptedException e) {
log.error("exception is {} "+e);
}
completableFuture.complete("pic.jpg");
};
}
下載檔案完成後, whenComplete()
方法將執行成功或失敗條件。
5. 結論
在這篇文章中,我們了解了ListenableFuture
和CompletableFuture
中的回呼機制。
我們發現,與CompletableFuture
相比, ListenableFuture
是一種更自然、更流暢的回呼 API。
我們可以自由選擇最適合我們使用案例的內容,因為**CompletableFuture
是核心 Java 的一部分,而ListenableFuture
是非常受歡迎的 Guava 程式庫的一部分**。
本文中使用的所有程式碼範例都可以在 GitHub 上找到。