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

类级别@Retryable下spring-retry重试方法判定逻辑及疑问

问题:Spring Retry类级别@Retryable注解的重试逻辑疑问

我在类级别添加了@Retryable注解,代码如下:

@Import({GrpcClientRetryConfig.class})
@Retryable(interceptor = "grpcClientRetryInterceptor")
@Component
public class Profile {
    public void method1() {
        method2();
    }

    public void method2() {
        val msg = "Hello World";
        System.out.println(msg);
        throw new RuntimeException("Testing");
    }
}

@EnableRetry
@Configuration
public class GrpcClientRetryConfig {
 @Bean
 public RetryOperationsInterceptor grpcClientRetryInterceptor() {
    val template = new RetryTemplate();
    val backOffPolicy = new ExponentialBackOffPolicy();
    // 设置指数退避参数
    val interceptor = new RetryOperationsInterceptor();
    val simpleRetry = new SimpleRetryPolicy();
    simpleRetry.setMaxAttempts(2);
    template.setRetryPolicy(simpleRetry);
    template.setBackOffPolicy(backOffPolicy);
    template.setListeners(new GrpcClientRetryListener[] {new GrpcClientRetryListener()});
    interceptor.setRetryOperations(template);
    return interceptor;
 }
}

案例1

val profile = applicationContext.getBean(Profile.class);
profile.method2(); // Hello World打印两次后抛出异常

案例2

val profile = applicationContext.getBean(Profile.class);
profile.method1(); // Hello World打印两次后抛出异常

我原本预期"Hello World"会被打印4次:method1()调用method2(),method2()重试2次后抛出异常给method1(),method1()重试时会再次调用method2()。但案例2中method1()并未被重试两次,请问类级别添加@Retryable时,Spring Retry是如何判定哪些方法需要重试的?


解答

一、Spring Retry判定重试方法的核心规则

当在类级别添加@Retryable注解时:

  1. 作用范围:类中所有public非静态方法都会被自动应用重试逻辑,无需在每个方法上单独添加注解。
  2. 触发条件:重试逻辑仅在方法通过Spring代理对象被外部调用时才会生效;如果是类内部方法之间的调用(如method1()直接调用method2()),调用的是原始对象的方法,不会触发被调用方法的代理切面,也就不会触发其重试逻辑。

二、案例2未达到预期的原因

你的预期错误在于认为method1()会触发重试,同时method2()也会在每次method1()调用时触发重试,但实际情况是:

  1. 案例2中method2()的重试是假象:如果method1()是通过代理对象调用的,method1()内部调用method2()是直接调用原始对象的方法,此时method2()的重试逻辑不会生效,应该只打印一次Hello World就抛出异常。但你案例2中打印了两次,说明你可能在测试时直接调用了method2(),或者代码存在其他配置问题。
  2. method1()未触发重试的真实原因:如果method1()确实通过代理对象被调用,但未触发重试,大概率是因为重试策略未捕获到method1()抛出的异常。不过SimpleRetryPolicy默认会捕获所有RuntimeException,所以更可能的原因是:
    • 你直接通过new Profile()创建对象,而非从Spring容器获取代理对象,导致@Retryable注解完全不生效;
    • 你的GrpcClientRetryListener中存在逻辑修改了重试行为,阻止了method1()的重试。

三、实现预期效果的修正方案

要让method1()重试时,每次都触发method2()的重试,需要让method1()内部调用method2()时使用代理对象:

@Component
public class Profile {
    @Autowired
    private ApplicationContext applicationContext;

    public void method1() {
        // 获取代理对象并调用method2()
        ((Profile) applicationContext.getBean(Profile.class)).method2();
        // 或者使用AopContext:需要在启动类添加@EnableAspectJAutoProxy(exposeProxy = true)
        // ((Profile) AopContext.currentProxy()).method2();
    }

    public void method2() {
        val msg = "Hello World";
        System.out.println(msg);
        throw new RuntimeException("Testing");
    }
}

这样修改后,method1()每次重试时,都会通过代理对象调用method2(),触发method2()的重试逻辑,最终会打印4次Hello World。


内容的提问来源于stack exchange,提问作者tuk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 16:18:09