Перейти к основному содержимому

Руководство по весенней повторной попытке

· 6 мин. чтения

1. Обзор

Spring Retry предоставляет возможность автоматического повторного вызова неудачной операции. Это полезно, когда ошибки могут быть временными (например, мгновенный сбой сети).

В этом руководстве мы увидим различные способы использования Spring Retry : аннотации, RetryTemplate и обратные вызовы.

2. Зависимости Maven

Начнем с добавления зависимости spring - retry в наш файл pom.xml :

<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>

Нам также нужно добавить Spring AOP в наш проект:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>

Посмотрите на Maven Central последние версии зависимостей spring-retry и spring-aspects .

3. Включение Spring Retry

Чтобы включить Spring Retry в приложении, нам нужно добавить аннотацию @EnableRetry в наш класс @Configuration :

@Configuration
@EnableRetry
public class AppConfig { ... }

4. Использование Spring Retry

4.1. @Retryable без восстановления

Мы можем использовать аннотацию @Retryable , чтобы добавить функциональность повтора к методам :

@Service
public interface MyService {
@Retryable(value = RuntimeException.class)
void retryService(String sql);

}

Здесь повторная попытка предпринимается при возникновении исключения RuntimeException .

Согласно поведению @Retryable по умолчанию повторная попытка может выполняться до трех раз с задержкой в одну секунду между повторными попытками.

4.2. @Retryable и @Recover

Теперь добавим метод восстановления с помощью аннотации @Recover :

@Service
public interface MyService {
@Retryable(value = SQLException.class)
void retryServiceWithRecovery(String sql) throws SQLException;

@Recover
void recover(SQLException e, String sql);
}

Здесь повторная попытка предпринимается при возникновении исключения SQLException . Аннотация @Recover определяет отдельный метод восстановления, когда метод @Retryable дает сбой с указанным исключением.

Следовательно, если метод retryServiceWithRecovery продолжает вызывать исключение SqlException после трех попыток, будет вызван метод recovery () .

Обработчик восстановления должен иметь первый параметр типа Throwable (необязательно) и тот же тип возвращаемого значения. `` Следующие аргументы заполняются из списка аргументов неудачного метода в том же порядке.

4.3. Настройка поведения @Retryable

Чтобы настроить поведение повтора, мы можем использовать параметры maxAttempts и backoff :

@Service
public interface MyService {
@Retryable( value = SQLException.class,
maxAttempts = 2, backoff = @Backoff(delay = 100))
void retryServiceWithCustomization(String sql) throws SQLException;
}

Будет до двух попыток и задержка в 100 миллисекунд.

4.4. Использование свойств пружины

Мы также можем использовать свойства в аннотации @Retryable .

Чтобы продемонстрировать это, мы увидим, как внедрить значения delay и maxAttempts в файл свойств.

Во-первых, давайте определим свойства в файле с именем retryConfig. свойства :

retry.maxAttempts=2
retry.maxDelay=100

Затем мы приказываем нашему классу @Configuration загрузить этот файл:

// ...
@PropertySource("classpath:retryConfig.properties")
public class AppConfig { ... }

Наконец, мы можем ввести значения retry.maxAttempts и retry.maxDelay в наше определение @Retryable :

@Service 
public interface MyService {
@Retryable( value = SQLException.class, maxAttemptsExpression = "${retry.maxAttempts}",
backoff = @Backoff(delayExpression = "${retry.maxDelay}"))
void retryServiceWithExternalizedConfiguration(String sql) throws SQLException;
}

Обратите внимание, что теперь мы используем maxAttemptsExpression и delayExpression вместо maxAttempts и delay .

5. Повторить шаблон

5.1. RetryOperations

Spring Retry предоставляет интерфейс RetryOperations , который предоставляет набор методов execute() :

public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;

...
}

RetryCallback , который является параметром execute() , представляет собой интерфейс, позволяющий вставлять бизнес-логику, которую необходимо повторить в случае сбоя : ``

public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}

5.2. Конфигурация RetryTemplate

RetryTemplate — это реализация RetryOperations .

Давайте настроим bean-компонент RetryTemplate в нашем классе @Configuration :

@Configuration
public class AppConfig {
//...
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();

FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000l);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);

return retryTemplate;
}
}

RetryPolicy определяет, когда операцию следует повторить .

SimpleRetryPolicy используется для повторных попыток определенное количество раз . С другой стороны, BackOffPolicy используется для управления отсрочкой между повторными попытками.

Наконец, FixedBackOffPolicy делает паузу на фиксированный период времени, прежде чем продолжить.

5.3. Использование RetryTemplate

Чтобы запустить код с обработкой повторных попыток, мы можем вызвать метод r etryTemplate.execute () : ``

retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
@Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});

Вместо анонимного класса мы можем использовать лямбда-выражение: ``

retryTemplate.execute(arg0 -> {
myService.templateRetryService();
return null;
});

6. Слушатели

Слушатели предоставляют дополнительные обратные вызовы при повторных попытках. И мы можем использовать их для различных сквозных проблем при разных повторных попытках.

6.1. Добавление обратных вызовов

Обратные вызовы предоставляются в интерфейсе RetryListener :

public class DefaultListenerSupport extends RetryListenerSupport {
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
logger.info("onClose);
...
super.close(context, callback, throwable);
}

@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
logger.info("onError");
...
super.onError(context, callback, throwable);
}

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

Обратные вызовы open и close поступают до и после всей повторной попытки, а onError применяется к отдельным вызовам RetryCallback .

6.2. Регистрация слушателя

Затем мы регистрируем наш слушатель ( DefaultListenerSupport) в нашем bean-компоненте RetryTemplate :

@Configuration
public class AppConfig {
...

@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
...
retryTemplate.registerListener(new DefaultListenerSupport());
return retryTemplate;
}
}

7. Проверка результатов

Чтобы закончить наш пример, давайте проверим результаты:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = AppConfig.class,
loader = AnnotationConfigContextLoader.class)
public class SpringRetryIntegrationTest {

@Autowired
private MyService myService;

@Autowired
private RetryTemplate retryTemplate;

@Test(expected = RuntimeException.class)
public void givenTemplateRetryService_whenCallWithException_thenRetry() {
retryTemplate.execute(arg0 -> {
myService.templateRetryService();
return null;
});
}
}

Как видно из тестовых логов, мы правильно настроили RetryTemplate и RetryListener :

2020-01-09 20:04:10 [main] INFO  o.b.s.DefaultListenerSupport - onOpen 
2020-01-09 20:04:10 [main] INFO o.foreach.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService()
2020-01-09 20:04:10 [main] INFO o.b.s.DefaultListenerSupport - onError
2020-01-09 20:04:12 [main] INFO o.foreach.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService()
2020-01-09 20:04:12 [main] INFO o.b.s.DefaultListenerSupport - onError
2020-01-09 20:04:12 [main] INFO o.b.s.DefaultListenerSupport - onClose

8. Заключение

В этой статье мы увидели, как использовать Spring Retry, используя аннотации, RetryTemplate и прослушиватели обратных вызовов.

Исходный код примеров доступен на GitHub .