类级别@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注解时:
- 作用范围:类中所有public非静态方法都会被自动应用重试逻辑,无需在每个方法上单独添加注解。
- 触发条件:重试逻辑仅在方法通过Spring代理对象被外部调用时才会生效;如果是类内部方法之间的调用(如
method1()直接调用method2()),调用的是原始对象的方法,不会触发被调用方法的代理切面,也就不会触发其重试逻辑。
二、案例2未达到预期的原因
你的预期错误在于认为method1()会触发重试,同时method2()也会在每次method1()调用时触发重试,但实际情况是:
- 案例2中
method2()的重试是假象:如果method1()是通过代理对象调用的,method1()内部调用method2()是直接调用原始对象的方法,此时method2()的重试逻辑不会生效,应该只打印一次Hello World就抛出异常。但你案例2中打印了两次,说明你可能在测试时直接调用了method2(),或者代码存在其他配置问题。 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
相关产品推荐
相关产品推荐

