在@Async注解方法中调用@Retryable方法失效问题求助
解决@Retryable在@Async调用时抛出异常的问题
遇到这种情况其实是Spring的AOP代理机制在“作祟”,咱们先搞清楚原因,再一步步解决:
问题根源
@Retryable和@Async都是基于Spring动态代理实现的,只有当你通过代理对象调用标注了注解的方法时,对应的切面逻辑(重试/异步)才会生效。如果是同一个类内的@Async方法直接调用@Retryable方法,或者异步调用时没走到代理对象,就会直接执行原始类的方法——这时候重试逻辑根本没被触发,异常自然会直接抛出来。
解决方案
1. 拆分服务类(最推荐的方案)
把@Async方法和@Retryable方法放到不同的服务类中,这样调用时会通过Spring注入的代理对象触发切面逻辑。
示例代码:
重试服务类
@Service public class RetryAndRecoverService { int counter = 0; String str = null; @Retryable(value = {FooException.class, BarException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 1)) public String retryWithException() { System.out.println("retryWithException - " + (counter++)); if (str == null) { throw new FooException("String is null"); } return str; } @Recover public String recover(FooException e) { return "Recovered from FooException"; } @Recover public String recover(BarException e) { return "Recovered from BarException"; } }
异步服务类
@Service public class AsyncTaskService { @Autowired private RetryAndRecoverService retryService; @Async public CompletableFuture<String> invokeRetryMethodAsync() { // 这里调用的是RetryAndRecoverService的代理对象,重试逻辑会正常触发 String result = retryService.retryWithException(); return CompletableFuture.completedFuture(result); } }
2. 调整代理执行顺序(适合同类调用场景)
如果不想拆分类,可以通过指定切面的执行顺序,让@Retryable的切面先于@Async生效。
- 在启动类的
@EnableAsync注解中设置优先级,让异步代理的优先级更低:
@SpringBootApplication @EnableRetry @EnableAsync(order = Ordered.LOWEST_PRECEDENCE) public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }
- 给重试切面指定更高的优先级(更小的order值):
@Configuration public class RetryConfig { @Bean public RetryInterceptorBuilder retryInterceptor() { return RetryInterceptorBuilder.stateless() .order(Ordered.HIGHEST_PRECEDENCE) .retryOn(FooException.class, BarException.class) .maxAttempts(5) .backOff(new FixedBackOffPolicy()); } }
3. 手动获取代理对象(同内类调用的临时方案)
在同一个类中,通过Spring上下文获取当前类的代理对象,再调用@Retryable方法:
@Service public class RetryAndRecoverService implements ApplicationContextAware { int counter = 0; String str = null; private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Async public CompletableFuture<String> callRetryAsync() { // 获取代理对象调用方法,触发重试逻辑 String result = applicationContext.getBean(RetryAndRecoverService.class).retryWithException(); return CompletableFuture.completedFuture(result); } @Retryable(value = {FooException.class, BarException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 1)) public String retryWithException() { System.out.println("retryWithException - " + (counter++)); if (str == null) { throw new FooException("String is null"); } return str; } // 省略@Recover方法... }
额外注意事项
- 确保启动类上已经添加了
@EnableRetry和@EnableAsync注解,这两个注解是启用对应功能的前提。 - 如果使用CompletableFuture处理异步返回,要确保异常能被正确捕获和处理,避免异步线程的异常被忽略。
内容的提问来源于stack exchange,提问作者Sabarish.K
相关产品推荐
相关产品推荐

