# `@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)` 方法異步執行任務,並在該任務完成時獲取返回值。