1. Overview
1.概述
Sometimes, we require code execution to be asynchronous for better application performance and responsiveness. Also, we may want to automatically re-invoke the code on any exception, as we expect to encounter occasional failures like a network glitch.
有时,为了提高应用程序的性能和响应速度,我们需要异步执行代码。此外,我们可能希望在出现异常时自动重新调用代码,因为我们预计会偶尔遇到网络故障等故障。
In this tutorial, we’ll learn to implement an asynchronous execution with automatic retry in a Spring application.
在本教程中,我们将学习在 Spring 应用程序中实现带有自动重试功能的异步执行。
We’ll explore Spring’s support for async and retry operations.
我们将探索 Spring 对 async 和 retry 操作的支持。
2. Example Application in Spring Boot
2.Spring Boot 示例应用程序
Let’s imagine we need to build a simple microservice that calls a downstream service to process some data.
假设我们需要构建一个简单的微服务,调用下游服务来处理一些数据。
2.1. Maven Dependencies
2.1.Maven 依赖项
First, we’ll need to include the spring-boot-starter-web maven dependency:
首先,我们需要包含 spring-boot-starter-web maven 依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.2. Implementing a Spring Service
2.2.实现 Spring 服务
Now, we’ll implement the EventService class’s method that calls another service:
现在,我们将实现 EventService 类调用另一个服务的方法:
public String processEvents(List<String> events) {
downstreamService.publishEvents(events);
return "Completed";
}
Then, let’s define the DownstreamService interface:
然后,让我们定义 DownstreamService 接口:
public interface DownstreamService {
boolean publishEvents(List<String> events);
}
3. Implementing Asynchronous Execution With Retry
3.通过重试实现异步执行
To implement asynchronous execution with retry, we’ll use Spring’s implementation.
为了实现带有重试功能的异步执行,我们将使用 Spring 的实现。
We’ll need to configure the application with the async and retry support.
我们需要为应用程序配置 async 和 retry 支持。
3.1. Adding Retry Maven Dependency
3.1.添加 Retry Maven 依赖项
Let’s add the spring-retry into the maven dependencies:
让我们将 spring-retry 添加到 maven 依赖项中:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.4</version>
</dependency>
3.2. @EnableAsync and @EnableRetry Configurations
3.2.@EnableAsync 和 @EnableRetry 配置
Next we need to include the @EnableAsync and @EnableRetry annotations:
接下来,我们需要包含 @EnableAsync 和 @EnableRetry 注释:
@Configuration
@ComponentScan("com.baeldung.asyncwithretry")
@EnableRetry
@EnableAsync
public class AsyncConfig {
}
3.3. Include the @Async and @Retryable Annotations
3.3.包含 @Async 和 @Retryable 注解
To execute a method asynchronously, we’ll need to use the @Async annotation. Similarly, we’ll annotate the method with the @Retryable annotation for retrying execution.
要异步执行方法,我们需要使用 @Async 注解。类似地,我们将使用 @Retryable 注解对方法进行注解,以重试执行.。
Let’s configure the above annotations in the above EventService method:
让我们在上述 EventService 方法中配置上述注释:
@Async
@Retryable(retryFor = RuntimeException.class, maxAttempts = 4, backoff = @Backoff(delay = 100))
public Future<String> processEvents(List<String> events) {
LOGGER.info("Processing asynchronously with Thread {}", Thread.currentThread().getName());
downstreamService.publishEvents(events);
CompletableFuture<String> future = new CompletableFuture<>();
future.complete("Completed");
LOGGER.info("Completed async method with Thread {}", Thread.currentThread().getName());
return future;
}
In the above code, we’re retrying the method in case of RuntimeException and returning the result as a Future object.
在上述代码中,如果出现 RuntimeException 异常,我们将重试该方法,并将结果作为 Future 对象返回。
We should note that we should use Future to wrap the response from any async method.
我们应该注意,我们应该使用 Future 来封装来自任何异步方法的响应。</em
We should note that the @Async annotation only works on a public method and should not be self-invoked within the same class. Self invoking the method will bypass the Spring proxy call and run it in the same thread.
我们应该注意,@Async注解仅适用于公共方法,并且不应在同一类中自行调用。自调用方法将绕过 Spring 代理调用,并在同一线程中运行。
4. Implement Test for the @Async and @Retryable
4.为 @Async 和 @Retryable 实施测试
Let’s test the EventService method and verify its asynchronous and retry behaviour with a few test cases.
让我们测试 EventService 方法,并通过几个测试用例验证其异步和重试行为。
First, we’ll implement a test case when there’s no error from the DownstreamService call:
首先,我们将在 DownstreamService 调用没有出错时实现一个测试用例:
@Test
void givenAsyncMethodHasNoRuntimeException_whenAsyncMethodIscalled_thenReturnSuccess_WithoutAnyRetry() throws Exception {
LOGGER.info("Testing for async with retry execution with thread " + Thread.currentThread().getName());
when(downstreamService.publishEvents(anyList())).thenReturn(true);
Future<String> resultFuture = eventService.processEvents(List.of("test1"));
while (!resultFuture.isDone() && !resultFuture.isCancelled()) {
TimeUnit.MILLISECONDS.sleep(5);
}
assertTrue(resultFuture.isDone());
assertEquals("Completed", resultFuture.get());
verify(downstreamService, times(1)).publishEvents(anyList());
}
In the above test, we’re waiting for the Future completion and then asserting the result.
在上述测试中,我们等待 Future 完成,然后断言结果。
Then, let’s run the above test and verify the test logs:
然后,让我们运行上述测试并验证测试日志:
18:59:24.064 [main] INFO com.baeldung.asyncwithretry.EventServiceIntegrationTest - Testing for async with retry execution with thread main
18:59:24.078 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Processing asynchronously with Thread SimpleAsyncTaskExecutor-1
18:59:24.080 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Completed async method with Thread SimpleAsyncTaskExecutor-1
From the above log, we confirm that the service method runs in a separate thread.
从上述日志中,我们可以确认服务方法是在单独的线程中运行的。
Next, we’ll implement another test case with the DownstreamService method throwing a RuntimeException:
接下来,我们将使用抛出 RuntimeException 的 DownstreamService 方法实现另一个测试用例:
@Test
void givenAsyncMethodHasRuntimeException_whenAsyncMethodIsCalled_thenReturnFailure_With_MultipleRetries() throws InterruptedException {
LOGGER.info("Testing for async with retry execution with thread " + Thread.currentThread().getName());
when(downstreamService.publishEvents(anyList())).thenThrow(RuntimeException.class);
Future<String> resultFuture = eventService.processEvents(List.of("test1"));
while (!resultFuture.isDone() && !resultFuture.isCancelled()) {
TimeUnit.MILLISECONDS.sleep(5);
}
assertTrue(resultFuture.isDone());
assertThrows(ExecutionException.class, resultFuture::get);
verify(downstreamService, times(4)).publishEvents(anyList());
}
Finally, let’s verify the above test case with the output logs:
最后,让我们用输出日志来验证上述测试用例:
19:01:32.307 [main] INFO com.baeldung.asyncwithretry.EventServiceIntegrationTest - Testing for async with retry execution with thread main
19:01:32.318 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Processing asynchronously with Thread SimpleAsyncTaskExecutor-1
19:01:32.425 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Processing asynchronously with Thread SimpleAsyncTaskExecutor-1
.....
From the above log, we confirm that the service method was re-executed asynchronously four times.
从上述日志中,我们可以确认服务方法被异步重新执行了四次。
5. Conclusion
5.结论
In this article, we’ve learned how to implement asynchronous method with the retry mechanism in Spring.
在本文中,我们学习了如何在 Spring 中使用重试机制实现异步方法 。</span
We’ve implemented this in an example application and tried a few tests to see how it handles different use cases. We’ve seen how the asynchronous code runs on its own thread and can automatically retry.
我们在一个示例应用程序中实现了这一功能,并进行了一些测试,以了解它如何处理不同的用例。我们已经看到异步代码是如何在自己的线程上运行并自动重试的。
As always, example code can be found over on GitHub.
一如既往,您可以在 GitHub 上找到示例代码。