1. Visão Geral
O Spring Retry fornece funcionalidade para recuperar automaticamente as operações com falha. Isso é útil em situações em que o erro pode ser transitório, como uma falha de rede temporária.
Neste tutorial, veremos várias maneiras de tentar novamente com Spring : Annotations, RetryTemplate e Callbacks.
2. Dependências do Maven
Vamos começar adicionando a dependência spring-retry ao nosso arquivo pom.xml :
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.0</version>
</dependency>
Também precisamos adicionar o Spring AOP ao nosso projeto:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
Confira o Maven Central para obter as versões mais recentes das dependências spring-retry e spring-aspects .
3. Ative a repetição de mola
Para ativar as tentativas do Spring em nosso aplicativo, precisamos adicionar a anotação @EnableRetry à nossa classe @Configuration :
@Configuration
@EnableRetry
public class AppConfig { ... }
4. Tente novamente usando o Spring
4.1. Sem recuperação @Retryable
Podemos adicionar funcionalidade de repetição a um método usando a anotação @Retryable :
@Service
public interface MyService {
@Retryable
void retryService(String sql);
}
Como não especificamos nenhuma exceção aqui, todas as exceções serão tentadas para serem repetidas. Além disso, quando o número máximo de tentativas for atingido e a exceção ainda estiver presente, será lançada uma ExhaustedRetryException.
De acordo com o comportamento padrão de @Retryable , as novas tentativas podem ocorrer até três vezes, com um atraso de um segundo entre as tentativas.
4.2. @Retryable e @Recover
Agora vamos adicionar um método de recuperação usando a anotação @Recover :
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class)
void retryServiceWithRecovery(String sql) throws SQLException;
@Recover
void recover(SQLException e, String sql);
}
Aqui, tente novamente quando SQLException for lançada . A anotação @Recover define um único método de recuperação quando um método @Retryable falha com a exceção especificada.
Portanto, se o método retryServiceWithRecovery continuar lançando um SqlException após três tentativas , o método retrieve() será chamado.
Um manipulador de recuperação deve ter um primeiro parâmetro do tipo Throwable (opcional) e um tipo de retorno do mesmo. Os parâmetros a seguir são preenchidos na mesma ordem da lista de parâmetros do método com falha.
4.3. Personalize o comportamento de @Retryable
Para personalizar o comportamento de repetição, podemos usar os parâmetros maxTry e backoff :
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
void retryServiceWithCustomization(String sql) throws SQLException;
}
Haverá um máximo de duas tentativas e um atraso de 100ms.
4.4. Usando as propriedades da mola
Também podemos usar propriedades na anotação @Retryable .
Para demonstrar isso, veremos como externalizar os valores de delay e max_attempts em um arquivo de propriedades.
Primeiro, vamos definir as propriedades em um arquivo chamado retryConfig . propriedades :
retry.maxAttempts=2
retry.maxDelay=100
Em seguida, instruímos nossa classe @Configuration a carregar este arquivo:
// ...
@PropertySource("classpath:retryConfig.properties")
public class AppConfig { ... }
Por fim, podemos injetar os valores de retry.maxTry e retry.maxDelay na definição @Retryable :
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttemptsExpression = "${retry.maxAttempts}",
backoff = @Backoff(delayExpression = "${retry.maxDelay}"))
void retryServiceWithExternalConfiguration(String sql) throws SQLException;
}
Observe que agora usamos maxTrysExpression e delayExpression em vez de maxTry e delay .
5. Repita o modelo
5.1. Repetir a operação
O Spring Retry fornece a interface RetryOperations , que fornece um conjunto de métodos execute() :
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
...
}
RetryCallback é um parâmetro de execute() e é uma interface que permite a lógica de negócios que precisa ser repetida quando uma inserção falha:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
5.2 . Repetir a configuração do modelo
RetryTemplate é a implementação de RetryOperations .
Vamos configurar um bean RetryTemplate na classe @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;
}
}
Uma política de repetição determina quando uma operação deve ser repetida .
A estratégia de repetição simples é usada para repetir um número fixo de vezes. Por outro lado, BackOffPolicy é usado para controlar a espera entre tentativas de repetição.
Finalmente, FixedBackOffPolicy faz uma pausa por um período fixo de tempo antes de continuar.
5.3. Usando modelos de repetição
Para executar o código com tratamento de repetição, podemos chamar o método retryTemplate.execute() :
retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
@Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});
Podemos usar expressões lambda em vez de classes anônimas:
retryTemplate.execute(arg0 -> {
myService.templateRetryService();
return null;
});
6. Público
O ouvinte fornece retornos de chamada adicionais ao tentar novamente. Podemos usá-los para várias preocupações transversais em diferentes tentativas.
6.1. Adicionar retornos de chamada
Os retornos de chamada são fornecidos na interface 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);
}
}
Os retornos de chamada abertos e fechados ocorrem antes e depois de toda a repetição, enquanto onError se aplica a uma única chamada RetryCallback .
6.2. Registrando ouvintes
Em seguida, registramos o ouvinte ( DefaultListenerSupport) em nosso bean RetryTemplate :
@Configuration
public class AppConfig {
...
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
...
retryTemplate.registerListener(new DefaultListenerSupport());
return retryTemplate;
}
}
7. Resultados do teste
Para completar nosso exemplo, vamos verificar os resultados:
@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;
});
}
}
Como pode ser visto no log de teste, configuramos corretamente RetryTemplate e RetryListener :
2020-01-09 20:04:10 [main] INFO o.b.s.DefaultListenerSupport - onOpen
2020-01-09 20:04:10 [main] INFO o.baeldung.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.baeldung.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. Conclusão
Neste artigo, vimos como usar o Spring Retry usando anotações, RetryTemplate e callback listeners.
O código-fonte desses exemplos está disponível no GitHub .