Spring 使用 for 循环重试

Spring Retry with for loop

我的 RetryTemplate 配置:

@Configuration
@EnableRetry
public class RetryTemplateConfig {

    @Value("${spring.retry.attempts}")
    private int maxAttempts;

    @Value("${spring.retry.period}")
    private long backOffPeriod;

    @Bean
    public RetryTemplate retryTemplate() {
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(maxAttempts);
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(backOffPeriod);
        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setRetryPolicy(retryPolicy);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        return retryTemplate;
    }
}

调用使用 Retry:

方法的计划方法
@Scheduled(cron = "${schedule.cron.update.users}")
    public void sendToUsers() throws Exception {
        log.info("Scheduled sending for users started");
        try {
            mailSender.sendToUsers();
        } catch (MessagingException | IOException | TemplateException e) {
            log.error("Error occurred while sending email message to users: {}", e.toString());
        }
        log.info("Scheduled sending for users finished");
    }

我想使用的方法RetryTemplate:

public void sendToUsers() throws Exception {
        String subject = reportGenerationService.getEmailSubjectForUser();
        Map<String, List<BadUtmMark>> utmMarksGroupedByEmail = userService.getUtmMarksGroupedByEmail(LocalDate.now());
        if (utmMarksGroupedByEmail.isEmpty()) {
            log.info("All's fine - no broken utm marks in database. Emails to users will not be send.");
        }
        for (Map.Entry<String, List<BadUtmMark>> pair : utmMarksGroupedByEmail.entrySet()) {
            retryTemplate.execute(retryContext -> {
                String emailTo = pair.getKey();
                List<BadUtmMark> badUtmMarks = pair.getValue();
                String report = reportGenerationService.getReportForUser(emailTo, badUtmMarks, template);
                MimeMessage mimeMessage = getMimeMessage(subject, report, Collections.singletonList(emailTo));
                log.info("Message will be sent to: {}; from: {}; with subject: {}", pair.getKey(), from, subject);
                mailSender.send(mimeMessage);
                return true;
            });
        }
    }

预期行为:我想为 5 个人发送电子邮件。如果发生错误,则再尝试向该用户发送电子邮件 5 次,然后如果重试耗尽,则继续为循环中的下一个用户发送电子邮件。

真正的行为:如果发生错误,服务将捕获异常并停止循环。

如果我将重试逻辑移至此方法:

@Scheduled(cron = "${schedule.cron.update.users}")
public void sendToUsers() throws Exception {
    log.info("Scheduled sending for users started");
    try {
        retryTemplate.execute(retryContext - > {
            log.warn("Sending email to users. Attempt: {}", retryContext.getRetryCount());
            mailSender.sendToUsers();
            return true;
        });
    } catch (MessagingException | IOException | TemplateException e) {
        log.error("Error occurred while sending email message to users: {}", e.toString());
    }
    log.info("Scheduled sending for users finished");
}

效果更好,但仍未达到我的预期。在这种情况下,如果发生错误,服务将尝试再发送电子邮件 5 次,但如果重试次数耗尽,服务将停止循环。因此,如果其中一位用户出现错误,该服务将尝试为该用户再发送 5 次,然后停止,忽略地图中的其他用户。

但是我想为地图中的每封电子邮件重试 5 次。我该怎么做?

在您的第一个版本中,请改用 execute...

    /**
     * Keep executing the callback until it either succeeds or the policy dictates that we
     * stop, in which case the recovery callback will be executed.
     *
     * @see RetryOperations#execute(RetryCallback, RecoveryCallback)
     * @param retryCallback the {@link RetryCallback}
     * @param recoveryCallback the {@link RecoveryCallback}
     * @throws TerminatedRetryException if the retry has been manually terminated by a
     * listener.
     */
    @Override
    public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
            RecoveryCallback<T> recoveryCallback) throws E {
        return doExecute(retryCallback, recoveryCallback, null);
    }
template.execute(context -> {
    ...
}, context -> {
    logger.error("Failed to send to ...");
});

如果回调正常退出,则失败被“恢复”并且不会重新抛出异常。