Хорошо, это на самом деле не о зиме, которая, как мы все знаем , уже наступила . Речь идет о 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 0 Attempts exhausted. Total: 1 Last 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 0 IO Exception callback: attempt 1 IO Exception callback: attempt 2 Attempts exhausted. Total: 3 Last exception: Test IO Exception |
Как мы видим, только IOException инициировал три попытки. Обратите внимание, что попытки пронумерованы от 0, потому что при выполнении обратного вызова попытка не исчерпывается, поэтому последняя попытка имеет # 2, а не # 3. Но на RecoveryCallback все попытки исчерпаны, поэтому контекст содержит 3 попытки.
Мы также видим, что RecoveryCallback не вызывается, когда попытки были успешными. То есть он вызывается только тогда, когда выполнение завершается с исключением.
RetryTemplate является синхронным, поэтому все выполнение происходит в нашем основном потоке. Вот почему я добавил блоки try / catch вокруг вызовов, чтобы программа без проблем выполнила все три примера. В противном случае политика повторных попыток перезапустит исключение после его последней неудачной попытки и остановит выполнение.
Существует также очень интересная CompositeRetryPolicy, которая позволяет добавлять несколько политик и делегатов для их вызова по порядку, один за другим. Это также может позволить создать довольно гибкую стратегию повторов, но это уже другая тема.
Я думаю, что spring-retry — это очень полезная библиотека, которая позволяет сделать обычные повторяющиеся задачи более предсказуемыми, тестируемыми и более простыми в реализации.
Ссылка: | Spring Retry, потому что зима идет от нашей партнершей по JCG Марины Чернявской из блога по разработке программного обеспечения и другим животным . |