Thursday, January 20, 2022

Spring Retry Configuration

Spring Retry Basic Configuration with @Retriable

public class RetryTemplateConfiguration {
  @SuppressWarnings("squid:S3749")
  private final RetryTemplateConfigProperties config;
  @SuppressWarnings("squid:S3749")
  private final RetryLoggingListener retryLoggingListener;

  @Bean
  public RetryTemplate retryTemplate() {
    //      FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
    //      fixedBackOffPolicy.setBackOffPeriod(2000l);
    //      retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
    final Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
    exceptionMap.put(RecoverableFailureException.class, true);
    final RetryTemplate retryTemplate = new RetryTemplate();
    retryTemplate.setRetryPolicy(new SimpleRetryPolicy(config.getTotalRetries(), exceptionMap));
    retryTemplate.registerListener(retryLoggingListener);
    final ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(config.getIntialInterval());
    backOffPolicy.setMaxInterval(config.getMaxInterval());
    backOffPolicy.setMultiplier(config.getMaxInterval());
    retryTemplate.setBackOffPolicy(backOffPolicy);
    return retryTemplate;
  }


@ConfigurationProperties(prefix = "abc.base.retry")
@Data
public class RetryTemplateConfigProperties {

  private int intialInterval;
  private int maxInterval;
  private int multiplier;
  private int totalRetries;
}


@Service
@Slf4j
public class RetryLoggingListener extends RetryListenerSupport {

  @Override
  public <T, E extends Throwable> void close(
      RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
    if (context.getRetryCount() > 0) {
      log.info(
          "%%% %%% %%% [{}] retry closing, count:{}",
          context.getAttribute("context.name"), context.getRetryCount());
    }
    super.close(context, callback, throwable);
  }

  @Override
  public <T, E extends Throwable> void onError(
      RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
    if (context.getRetryCount() > 0) {

      log.info(
          "%%% %%% %%% ["
              + context.getAttribute("context.name")
              + "] retry onError, count: {}"
              + context.getRetryCount()
              + ", exception type:"
              + throwable.getClass(),
          throwable);
    }
    super.onError(context, callback, throwable);
  }

  @Override
  public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
    log.info("%%% %%% %%%, retry opening...");
    return super.open(context, callback);
  }
}



@ExtendWith(SpringExtension.class)
@ContextConfiguration
@ActiveProfiles("retry")
class SpringRetryTest {

  @Autowired private CallerService callerService;

  @MockBean private RetriableService retriableService;

  @BeforeAll
  public static void setUp() {
    System.setProperty("spring.profiles.active", "retry");
  }

  @Test
  void retriesAfterOneFailAndThenPass() {
    callerService.intialCall("name");
    verify(retriableService, times(3)).retriableMethod("name");
  }

  @Configuration
  @EnableRetry
  @EnableAspectJAutoProxy(proxyTargetClass = true)
  @Profile("retry")
  public static class Application {
    private final RetriableService retriableService;

    public Application(RetriableService bService) {
      this.retriableService = bService;
    }

    @SuppressWarnings("unchecked")
    @Bean
    public CallerService aService() {
      when(retriableService.retriableMethod("name")).thenThrow(RecoverableFailureException.class).thenThrow(RecoverableFailureException.class).thenReturn("full name");
      return Mockito.spy(new CallerServiceImpl(retriableService));
    }

    @Bean
    public RetryLoggingListener retryLoggingListener() {
      return new RetryLoggingListener();
    }
  }
}


public interface CallerService {
  String intialCall(String name);
}


@Service
public class CallerServiceImpl implements CallerService {

  private final RetriableService retriableService;

  public CallerServiceImpl(RetriableService retriableService) {
    this.retriableService = retriableService;
  }

  @Override
  @Retryable(
      value = {RecoverableFailureException.class},
      maxAttempts = 6,
      backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 1800000),
      listeners = {"retryLoggingListener"})
  public String intialCall(String name) {
    return this.retriableService.retriableMethod(name);
  }
}

public interface RetriableService {
  String retriableMethod(String name);
}

No comments:

Post a Comment