Spring Boot @Retryable监听器未触发重试时仍被调用问题
问题:Spring Retry监听器在未触发重试时仍执行open/close方法
我在Service类中使用@Retryable注解实现数据库保存方法的重试逻辑,希望仅在触发重试行为时记录相关日志,但发现即使方法正常执行、未触发任何重试,配置的RetryListener中的open()和close()方法仍会被调用,导致输出了不必要的日志。
相关代码
PeopleService.java
package com.test.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; @Service public class PeopleService { @Autowired private PeopleRepository peopleRepository; @Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 200), listeners = {"retryListener"}) public void addPeople() throws Exception{ People p = new People(20, "James", "bond"); peopleRepository.save(p); } }
RetryListener.java
package com.test.demo; import lombok.extern.slf4j.Slf4j; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.listener.RetryListenerSupport; import org.springframework.stereotype.Component; @Slf4j @Component class RetryListener extends RetryListenerSupport { @Override public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { System.out.println("Unable to recover from Exception"); System.out.println("Error "); super.close(context, callback, throwable); } @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { System.out.println("Exception Occurred, Retry Count " + context.getRetryCount()); super.onError(context, callback, throwable); } @Override public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) { System.out.println("Exception Occurred, Retry Session Started "); return super.open(context, callback); } }
Demo.java(调用逻辑)
package com.test.demo; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.beans.factory.annotation.Autowired; @Slf4j @Component public class Demo { @Autowired private PeopleService peopleService; @Scheduled(initialDelay = 5000L, fixedDelay = 1000L) private void perMinuteStatsTask() { try { System.out.println("adding people"); peopleService.addPeople(); }catch(Exception ex) { ex.printStackTrace(); } } }
现象说明
无论数据库保存是否成功(即是否触发重试),控制台都会输出监听器open()和close()方法的日志,示例输出如下:
adding people Exception Occurred, Retry Session Started Hibernate: insert into people (age, first_name, last_name) values (?, ?, ?) Unable to recover from Exception Error
注:onError()方法仅在触发重试时才会执行,因此正常执行时不会输出该方法的日志。
原因分析及解决方案
原因
Spring Retry的RetryListener各方法执行时机是固定设计:
open():重试会话初始化完成后立即执行,与后续是否触发重试无关;close():重试会话结束时执行,无论方法最终成功还是失败;onError():仅当方法抛出异常且重试策略判定需要重试时才执行。
当前监听器日志未做条件判断,导致正常执行时也输出了错误相关的日志。
修改后的RetryListener.java
package com.test.demo; import lombok.extern.slf4j.Slf4j; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.listener.RetryListenerSupport; import org.springframework.stereotype.Component; @Slf4j @Component class RetryListener extends RetryListenerSupport { @Override public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { // 仅当有异常发生(触发重试或最终执行失败)时打印日志 if (throwable != null) { System.out.println("Unable to recover from Exception"); System.out.println("Error: " + throwable.getMessage()); } super.close(context, callback, throwable); } @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { // 此方法仅在触发重试时执行,直接输出重试日志 System.out.println("Exception Occurred, Retry Count " + context.getRetryCount()); super.onError(context, callback, throwable); } @Override public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) { // 若不需要每次方法调用都打印会话启动日志,可直接注释;若需追踪重试会话,可保留但修改日志描述 // System.out.println("Retry Session Started"); return super.open(context, callback); } }
核心修改点
- 在
close()方法中增加throwable != null判断,仅当方法执行出现异常时才输出错误恢复日志; - 调整
open()方法的日志逻辑:若不需要追踪所有重试会话,可直接移除日志输出;若需要保留,建议修改日志描述避免误解(比如改为“Retry Session Started”); onError()方法本身仅在触发重试时执行,原有逻辑无需修改。
修改后,只有当方法触发重试或最终执行失败时,才会输出对应的追踪日志,正常执行时不会产生多余日志。
内容的提问来源于stack exchange,提问作者quidstone
相关产品推荐
相关产品推荐

