Хорошо, это на самом деле не о зиме, которая, как мы все знаем , уже наступила . Речь идет о Spring Retry, небольшой библиотеке Spring Framework, которая позволяет нам добавлять функциональность повтора к любой задаче, которую следует повторить.
Здесь есть очень хорошее руководство , объясняющее, как настраиваются простые повторные попытки и восстановление. Он очень хорошо объясняет, как добавить зависимость при повторном запуске , использовать аннотации @Retryable и @Recover и использовать RetryTemplate с простыми политиками. Я хотел бы остановиться на немного более сложном случае, когда мы действительно хотим применить другое поведение повторения в зависимости от типа исключения. Это имеет смысл, потому что мы можем знать, что некоторые исключения восстанавливаемы, а некоторые нет, и поэтому не имеет особого смысла пытаться восстановить их. Для этого есть специальная реализация стратегии повторения, которая называется ExceptionClassifierRetryPolicy , которая используется с Spring RetryTemplate .
Давайте предположим, что мы можем восстановить только исключения IO и пропустить все остальные . Мы создадим три класса для расширения RetryCallback и один класс для расширения RecoveryCallback, чтобы лучше показать, что происходит внутри:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
private class SuccessCallback implements RetryCallback<Boolean, RuntimeException> { @Override public Boolean doWithRetry(RetryContext context) throws RuntimeException { System.out.println("Success callback: attempt " + context.getRetryCount()); return true; } } private class ExceptionCallback implements RetryCallback<Boolean, Exception> { @Override public Boolean doWithRetry(RetryContext context) throws Exception { System.out.println("Exception callback: attempt " + context.getRetryCount()); throw new Exception("Test Exception"); } } private class SpecificExceptionCallback implements RetryCallback<Boolean, IOException> { @Override public Boolean doWithRetry(RetryContext context) throws IOException { System.out.println("IO Exception callback: attempt " + context.getRetryCount()); throw new IOException("Test IO Exception"); } } private class LoggingRecoveryCallback implements RecoveryCallback<Boolean> { @Override public Boolean recover(RetryContext context) throws Exception { System.out.println("Attempts exhausted. Total: " + context.getRetryCount()); System.out.println("Last exception: " + Optional.ofNullable(context.getLastThrowable()) .orElse(new Throwable("No exception thrown")).getMessage()); System.out.println("\n"); return false; } } |
Затем мы настраиваем наш RetryTemplate . Мы будем использовать SimpeRetryPolicy с фиксированным числом попыток для IOException и NeverRetryPolicy, который просто разрешает начальную попытку для всего остального.
|
01
02
03
04
05
06
07
08
09
10
11
|
* We want to retry on IOException only. Other Exceptions won't be retried. IOException will be retried three times, counting the initial attempt. */ final ExceptionClassifierRetryPolicy exRetryPolicy = new ExceptionClassifierRetryPolicy(); exRetryPolicy.setPolicyMap(new HashMap<Class<? extends Throwable>, RetryPolicy>() {{ put(IOException.class, new SimpleRetryPolicy(3)); put(Exception.class, new NeverRetryPolicy()); }}); retryTemplate.setRetryPolicy(exRetryPolicy); |
Теперь нам нужно использовать эти обратные вызовы, чтобы продемонстрировать, как они работают. Сначала успешное выполнение, которое очень просто:
|
1
2
3
|
// we do not catch anything here System.out.println("\n*** Executing successfull callback..."); retryTemplate.execute(new SuccessCallback(), new LoggingRecoveryCallback()); |
Выход для него выглядит следующим образом:
|
1
2
|
*** Executing successfull callback...Success callback: attempt 0 |
Тогда исключение :
|
1
2
3
4
5
6
7
|
// we catch Exception to allow the program to continue System.out.println("\n*** Executing Exception callback..."); try { retryTemplate.execute(new ExceptionCallback(), new LoggingRecoveryCallback()); } catch (Exception e) { System.out.println("Suppressed Exception"); } |
|
1
2
3
4
|
*** Executing Exception callback...Exception callback: attempt 0Attempts exhausted. Total: 1Last exception: Test Exception |
И наконец наше IOException :
|
1
2
3
4
5
6
7
|
// we catch IOException to allow the program to continue System.out.println("\n*** Executing IO Exception callback..."); try { retryTemplate.execute(new SpecificExceptionCallback(), new LoggingRecoveryCallback()); } catch (IOException e) { System.out.println("Suppressed IO Exception"); } |
|
1
2
3
4
5
6
|
*** Executing IO Exception callback...IO Exception callback: attempt 0IO Exception callback: attempt 1IO Exception callback: attempt 2Attempts exhausted. Total: 3Last exception: Test IO Exception |
Как мы видим, только IOException инициировал три попытки. Обратите внимание, что попытки пронумерованы от 0, потому что при выполнении обратного вызова попытка не исчерпывается, поэтому последняя попытка имеет # 2, а не # 3. Но на RecoveryCallback все попытки исчерпаны, поэтому контекст содержит 3 попытки.
Мы также видим, что RecoveryCallback не вызывается, когда попытки были успешными. То есть он вызывается только тогда, когда выполнение завершается с исключением.
RetryTemplate является синхронным, поэтому все выполнение происходит в нашем основном потоке. Вот почему я добавил блоки try / catch вокруг вызовов, чтобы программа без проблем выполнила все три примера. В противном случае политика повторных попыток перезапустит исключение после его последней неудачной попытки и остановит выполнение.
Существует также очень интересная CompositeRetryPolicy, которая позволяет добавлять несколько политик и делегатов для их вызова по порядку, один за другим. Это также может позволить создать довольно гибкую стратегию повторов, но это уже другая тема.
Я думаю, что spring-retry — это очень полезная библиотека, которая позволяет сделать обычные повторяющиеся задачи более предсказуемыми, тестируемыми и более простыми в реализации.
| Ссылка: | Spring Retry, потому что зима идет от нашей партнершей по JCG Марины Чернявской из блога по разработке программного обеспечения и другим животным . |