1. 概述
有时,为了提升应用性能和响应性,我们需要实现异步代码执行。此外,我们可能希望在遇到诸如网络故障等偶尔的异常时,自动重新调用代码。本教程将教你如何在Spring应用中实现异步执行与自动重试。
我们将探讨Spring对async
和retry
操作的支持。
2. 示例Spring Boot应用
假设我们要构建一个简单的微服务,它调用下游服务处理一些数据。
2.1. Maven依赖项
首先,我们需要添加spring-boot-starter-web
的Maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.2. 实现Spring服务
现在,我们来实现EventService
类的方法,该方法调用另一个服务:
public String processEvents(List<String> events) {
downstreamService.publishEvents(events);
return "Completed";
}
接下来,定义DownstreamService
接口:
public interface DownstreamService {
boolean publishEvents(List<String> events);
}
3. 实现异步执行与重试
使用Spring的实现来实现异步执行与重试。
我们需要配置应用程序以支持async
和retry
。
3.1. 添加Retry
Maven依赖
加入spring-retry
到Maven依赖中:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.4</version>
</dependency>
3.2. @EnableAsync
和@EnableRetry
配置
接下来,需要包含@EnableAsync
和@EnableRetry
注解:
@Configuration
@ComponentScan("com.baeldung.asyncwithretry")
@EnableRetry
@EnableAsync
public class AsyncConfig {
}
3.3. 使用@Async
和@Retryable
注解
要异步执行方法,我们需要使用@Async
注解。同样,为重试执行方法添加@Retryable
注解:
在上述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;
}
在这段代码中,我们在RuntimeException
发生时重试方法,并返回一个Future
对象。
请注意,我们应该使用Future
(Java Future)包装来自任何异步方法的结果。
注意:@Async
注解仅适用于公共方法,且不能在同一个类内部自我调用。如果自我调用,将跳过Spring代理调用,在同一线程中运行方法。
4. 测试@Async
和@Retryable
现在,让我们测试EventService
方法,通过几个测试用例验证其异步和重试行为。
首先,当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());
}
在这个测试中,我们等待Future
完成,然后进行断言。
接着,运行上述测试并检查日志输出:
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
从日志中,我们确认服务方法在单独的线程中运行。
接下来,我们实现一个DownstreamService
方法抛出RuntimeException
的测试用例:
@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());
}
最后,验证上述测试用例的日志输出:
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
.....
从日志中,我们确认服务方法被异步重试了四次。
5. 总结
在这篇文章中,我们学习了如何在Spring中实现带有重试机制的异步方法。我们在示例应用中实现了这一点,并尝试了几种情况,观察它如何处理不同的场景。我们了解了异步代码如何在独立线程上运行,并能自动重试。
如往常一样,示例代码可在GitHub上找到。