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

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);
    }
}

核心修改点

  1. 在close()方法中增加throwable != null判断,仅当方法执行出现异常时才输出错误恢复日志;
  2. 调整open()方法的日志逻辑:若不需要追踪所有重试会话,可直接移除日志输出;若需要保留,建议修改日志描述避免误解(比如改为“Retry Session Started”);
  3. onError()方法本身仅在触发重试时执行,原有逻辑无需修改。

修改后,只有当方法触发重试或最终执行失败时,才会输出对应的追踪日志,正常执行时不会产生多余日志。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 19:20:36