You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:03:17