# `@Async` leads to null
[TOC]
###### tags: `chatgpt`
---
## TL;DR
> Controller:
> ```
> @GetMapping
> public List<InAppMail> index(@RequestParam(defaultValue = "1") final int p, @RequestParam(defaultValue = "10") final int s) {
> return mailService.load(
> p < 1 ? 0 : p - 1,
> s
> );
> }
> ```
>
> Service:
> ```
> @Async
> @Transactional(readOnly = true)
> public List<InAppMail> load(final int p, final int s) {
> return inAppMailRepository.
> findAll(
> PageRequest.of(
> p,
> s
> )
> ).
> getContent();
> }
> ```
>
> 為何 service method 有 `@Async` 時會回傳 `null`,然而沒有 `@Async` 時不會
當在 Spring Service 方法上添加 `@Async` 注解時,它會使該方法的執行在另一個線程中,而不是在當前線程中執行。當控制器調用 Service 方法時,Service 方法可能仍在運行,當控制器返回結果時,Service 方法還沒有完成。因此,Service 方法將不會返回任何結果,並且控制器將接收到一個 `null` 結果。
解決這個問題的方法是讓控制器等待 Service 方法完成。一種方法是將 Service 方法返回的 `Future` 對象傳遞給控制器,然後使用 `Future` 對象的 `get` 方法等待它的完成。另一種方法是使用 Spring 的 `DeferredResult` 或 `CompletableFuture` 來處理異步返回值,以便控制器能夠在 Service 方法完成後繼續處理返回值。
如果 Service 方法不是異步的,它將在控制器調用它時立即運行,並且在返回結果之前完成。這意味著控制器將收到正確的返回值,而不是 `null`。
## 分別用 `Future` 跟 `CompletableFuture` 重寫以上的代碼
使用 `Future` 重寫以上代碼可以這樣實現:
Controller:
```java
@GetMapping
public List<InAppMail> index(@RequestParam(defaultValue = "1") final int p, @RequestParam(defaultValue = "10") final int s) throws InterruptedException, ExecutionException {
Future<List<InAppMail>> future = mailService.load(p < 1 ? 0 : p - 1, s);
// 等待 Service 方法執行完畢,獲取返回值
return future.get();
}
```
Service:
```java
@Transactional(readOnly = true)
public Future<List<InAppMail>> load(final int p, final int s) {
// 使用 Java 的 Executors 工具類創建一個新的線程執行該方法
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<List<InAppMail>> future = executor.submit(() -> {
return inAppMailRepository.findAll(PageRequest.of(p, s)).getContent();
});
executor.shutdown();
return future;
}
```
使用 `CompletableFuture` 重寫以上代碼可以這樣實現:
Controller:
```java
@GetMapping
public CompletableFuture<List<InAppMail>> index(@RequestParam(defaultValue = "1") final int p, @RequestParam(defaultValue = "10") final int s) {
return mailService.load(p < 1 ? 0 : p - 1, s);
}
```
Service:
```java
@Transactional(readOnly = true)
public CompletableFuture<List<InAppMail>> load(final int p, final int s) {
return CompletableFuture.supplyAsync(() -> {
return inAppMailRepository.findAll(PageRequest.of(p, s)).getContent();
});
}
```
使用 `CompletableFuture` 可以更簡單地實現異步返回值,它的接口更加簡潔,而且可以更加自由地使用 lambda 表達式實現異步任務。此外,`CompletableFuture` 還提供了更多的方法來處理異步返回值,例如使用 `thenApply` 方法處理結果,使用 `exceptionally` 方法處理異常,以及使用 `whenComplete` 方法處理完成事件等。
## 舉個例子實現 `CompletableFuture.completeAsync(Supplier<? extends T> supplier)` 的用法
`CompletableFuture.completeAsync(Supplier<? extends T> supplier)` 方法可以用來異步執行一個 `Supplier` 並將結果作為 `CompletableFuture` 的返回值。
以下是一個使用 `completeAsync(Supplier)` 方法的示例,該示例使用異步方式從 API 獲取一個用戶名單,並將其轉換為用戶對象列表:
```java
public class UserService {
private final RestTemplate restTemplate;
public UserService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public CompletableFuture<List<User>> getUsersAsync() {
return CompletableFuture.supplyAsync(() -> {
// 異步調用API,獲取用戶名單
ResponseEntity<List<String>> response = restTemplate.exchange(
"https://api.example.com/users",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<String>>() {}
);
List<String> usernames = response.getBody();
// 將用戶名單轉換為用戶對象列表
List<User> users = usernames.stream().map(username -> {
// 異步調用API,獲取用戶詳細信息
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> {
ResponseEntity<User> userResponse = restTemplate.getForEntity(
"https://api.example.com/users/" + username,
User.class
);
return userResponse.getBody();
});
try {
// 等待用戶詳細信息獲取完畢
return userFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
return users;
});
}
}
```
在這個示例中,`getUsersAsync()` 方法返回一個 `CompletableFuture<List<User>>` 對象。它使用 `supplyAsync(Supplier)` 方法異步獲取用戶名單,並將結果轉換為用戶對象列表。
在轉換用戶名單為用戶對象列表時,它使用了另一個異步操作。它使用了 `supplyAsync(Supplier)` 方法異步獲取用戶詳細信息,並使用 `get()` 方法等待該操作完成。由於 `get()` 方法會阻塞當前線程,所以它需要在另一個線程中執行,這就是為什麼它使用了另一個異步操作的原因。
這個示例演示了如何使用 `CompletableFuture.completeAsync(Supplier)` 方法異步執行任務,並在該任務完成時獲取返回值。