1. 概述
Spring Retry 提供自動重試失敗操作的能力。 這在錯誤可能是暫時的(例如短暫的網絡故障)時非常有用。 但是,請注意,重試與斷路器不同。
在本教程中,我們將看到使用 Spring Retry 的各種方法:註解、RetryTemplate 和回調。
2. Maven 依賴
讓我們首先將 spring-retry 依賴添加到我們的 pom.xml 文件中:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.3</version>
</dependency>我們還需要在項目中添加 spring-boot-starter-aspectj 依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.1.5</version>
</dependency>此外,spring-aspects 依賴項至關重要,因為它會自動配置 Spring Boot 中的面向切面編程 (AOP)。請訪問 Maven Central 以獲取 spring-retry 和 spring-aspects 依賴項的最新版本。
3. 啓用 Spring Retry
為了在應用程序中啓用 Spring Retry,我們需要將 @EnableRetry 註解添加到我們的 @Configuration 類中:
@Configuration
@EnableRetry
public class AppConfig { ... }4. 使用 Spring Retry
Spring Retry 是一個用於簡化重試邏輯的庫。它允許你輕鬆地重試失敗的請求,而無需編寫複雜的重試策略。Spring Retry 提供了多種重試模式,包括基於時間的重試、基於異常的重試和基於條件的重試。
核心概念
- RetryTemplate: RetryTemplate 是 Spring Retry 的核心類,它封裝了重試邏輯,並允許你配置重試行為。
- RetryTemplateBuilder: RetryTemplateBuilder 允許你配置 RetryTemplate 的各種屬性,例如重試次數、延遲時間、最大延遲時間等。
- RetryCallback: RetryCallback 是一個接口,它定義了重試邏輯。你必須實現這個接口,並提供重試執行的代碼。
- RetryEvent: RetryEvent 是一個事件,它記錄了重試的詳細信息,例如重試次數、延遲時間、異常類型等。
配置 RetryTemplate
你可以使用 RetryTemplateBuilder 配置 RetryTemplate 的各種屬性。以下是一些常用的屬性:
retryable: 如果該屬性設置為true,則在重試後繼續執行方法。如果設置為false,則在重試後拋出異常。backoffExponent: 控制重試之間的延遲時間。默認值為 1.0。maxAttempts: 控制重試的最大次數。delay: 控制重試之間的延遲時間,單位為毫秒。
示例代碼
@Component
public class MyService {
@Retryable(
whatException = {IOException.class, RuntimeException.class},
backoff = {BackoffStrategy.fixed(500, 3), BackoffStrategy.exponential(3, 3)}
)
public void doSomething() throws IOException {
// ... 你的代碼 ...
}
}
在這個例子中,doSomething() 方法在發生 IOException 或 RuntimeException 時,會嘗試重試三次。第一次重試的延遲為 500 毫秒,第二次重試的延遲為 500 * 2 毫秒,第三次重試的延遲為 500 * 4 毫秒。
4.1. 不帶恢復的 @Retryable 註解
我們可以使用 @Retryable 註解為方法添加重試功能:
@Service
public interface MyService {
@Retryable
void retryService();
}由於我們未指定任何異常情況,因此將嘗試針對所有異常進行重試。根據 @Retryable 的默認行為,重試可能會最多發生三次,每次重試之間間隔一秒。此外,最大嘗試次數為 3,包括初始失敗調用和兩個後續重試。如果達到最大嘗試次數且仍存在異常,則會拋出 ExhaustedRetryException。
4.2. @Retryable 和 @Recover
現在,我們將使用 @Recover 註解添加一個恢復方法:
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class)
void retryServiceWithRecovery(String sql) throws SQLException;
@Recover
void recover(SQLException e, String sql);
}在這裏,當拋出 SQLException 時,會嘗試重試。註解 @Recover 定義了一個單獨的恢復方法,當帶有 @Retryable 註解的方法由於指定異常而失敗時生效。
因此,如果 retryServiceWithRecovery 方法在三次重試後仍然拋出 SqlException 異常,則將調用 recover() 方法。
恢復處理程序應具有第一個參數為 Throwable 類型(可選)和相同的返回類型。其餘參數將按照失敗方法的參數列表順序填充。
4.3. 自定義 @Retryable 的行為
為了自定義重試行為,我們可以使用參數 <em >maxAttempts</em > 和 <em >backoff</em >>。
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
void retryServiceWithCustomization(String sql) throws SQLException;
}在上述示例中,會有最多兩次嘗試,並在嘗試之間間隔 100 毫秒。
4.4. 使用 Spring 屬性
我們可以使用屬性在 <em @Retryable</em> 註解中。
為了演示這一點,我們將如何將 <em delay</em> 和 <i>maxAttempts</em> 的值外部化到屬性文件中。
首先,讓我們在名為 <em retryConfig</em> 的文件中定義屬性:
retry.maxAttempts=2
retry.maxDelay=100我們隨後指示我們的 @Configuration 類加載此文件:
// ...
@PropertySource("classpath:retryConfig.properties")
public class AppConfig { ... }最後,我們可以將 retry.maxAttempts 和 retry.maxDelay 的值注入到我們的 @Retryable 定義中。
@Service
public interface MyService {
@Retryable(retryFor = SQLException.class, maxAttemptsExpression = "${retry.maxAttempts}",
backoff = @Backoff(delayExpression = "${retry.maxDelay}"))
void retryServiceWithExternalConfiguration(String sql) throws SQLException;
}請注意,我們現在使用 maxAttemptsExpression 和 delayExpression 代替 maxAttempts 和 delay。
4.5. 打印重試計數
為了在 Spring 中使用 @Retryable 註解時記錄重試計數,可以在方法中利用 RetrySynchronizationManager.getContext().getRetryCount() 來打印當前重試嘗試次數:
@Override
public void retryService() {
logger.info("Retry Number: "+ RetrySynchronizationManager.getContext().getRetryCount());
logger.info("throw RuntimeException in method retryService()");
throw new RuntimeException();
}在上述邏輯中,我們添加了一條關於重試次數的日誌信息。如果執行 retryService(),我們將獲取每個重試的日誌,以及其重試計數:
Retry Number: 0
throw RuntimeException in method retryService()
Retry Number: 1
throw RuntimeException in method retryService()
Retry Number: 2
throw RuntimeException in method retryService()正如日誌中所示,每次重試都會打印重試次數。
5. RetryTemplate
The RetryTemplate is a bean that allows you to retry an operation if it fails. It provides a convenient way to handle transient failures, such as network glitches or temporary service unavailability.
Key Features:
- Automatic Retries: The
RetryTemplateautomatically retries the operation a specified number of times. - Customizable Retry Strategy: You can configure the retry strategy, including the number of retries, delay between retries, and the conditions under which retries should be attempted.
- Support for Different Types of Operations: The
RetryTemplatecan be used with various types of operations, including method calls, HTTP requests, and database transactions.
Configuration:
You can configure the RetryTemplate using the following properties:
backoffInterval: The initial delay between retries in milliseconds.maxAttempts: The maximum number of retries.backoffPolicy: The policy used to determine the delay between retries. Common backoff policies include fixed delay, exponential backoff, and jittered backoff.
Example:
@Retryable(with = {Exception.class})
public String doSomethingRisky() {
// ... some risky operation ...
if (/* operation failed */) {
throw new RuntimeException("Operation failed");
}
return "Operation successful";
}
Note: The RetryTemplate is often used in conjunction with the @Retryable annotation to simplify the implementation of retry logic.
5.1. 重試操作 (RetryOperations)
Spring Retry 提供 RetryOperations 接口,該接口提供了一組 execute() 方法。
public interface RetryOperations {
<T> T execute(RetryCallback<T, ? extends Throwable> retryCallback) throws Exception;
...
}RetryCallback,是 execute() 的一個參數,它是一個接口,允許在失敗時插入需要重試的業務邏輯:
public interface RetryCallback<T, E extends Throwable> {
T doWithRetry(RetryContext context) throws Throwable;
}5.2. 介紹 RetryPolicy 建造器 API 和工廠方法
RetryTemplate 是 RetryOperations 的實現。
Spring Retry 通過引入工廠方法和建造器 API,簡化了 RetryPolicy 和 BackOffPolicy 的創建 (從 Spring Retry 2.0.0 開始,對於某些策略)。 這使得配置更加清晰和流暢。
RetryPolicy 確定操作應何時重試。 SimpleRetryPolicy 用於重試固定次數。 嘗試次數包括初始嘗試。 對於 SimpleRetryPolicy,我們可以使用構造函數中的 maxAttempts 參數來設置最大嘗試次數。 這也包括配置零重試的功能 (new SimpleRetryPolicy(1)).
BackOffPolicy 用於控制重試嘗試之間的退避。 FixedBackOffPolicy 在繼續之前暫停固定時間。
5.3. 重試模板配置
讓我們使用新的 Builder API 在我們的 @Configuration 類中配置一個 RetryTemplate Bean:
@Configuration
public class AppConfig {
private FixedBackOffPolicy fixedBackOffPolicy(long backOffPeriod) {
FixedBackOffPolicy policy = new FixedBackOffPolicy();
policy.setBackOffPeriod(backOffPeriod);
return policy;
}
@Bean
public RetryTemplate retryTemplate() {
return RetryTemplate.builder()
.maxAttempts(3)
.customBackoff(fixedBackOffPolicy(2000L))
.withListener(new DefaultListenerSupport())
.build();
}
@Bean
public RetryTemplate retryTemplateNoRetry() {
return RetryTemplate.builder()
.maxAttempts(1)
.customBackoff(fixedBackOffPolicy(100L))
.build();
}
}在示例中,我們使用了一個輔助方法來處理 FixedBackOffPolicy。 此外,我們使用 Builder API 的 RetryTemplate.builder(),它支持一個 maxAttempts() 方法,我們已將其配置為最多 3 次嘗試。 “maxAttempts” 包括初始嘗試和所有重試。
此外,我們演示了新的 Builder API 接受 maxAttempts(1) 在 RetryTemplateBuilder 構建器中。 maxAttempts() 中 1 的值 意味着初始調用是唯一的嘗試,不會發生任何重試。
5.4. 使用 RetryTemplate
要運行帶有重試處理的代碼,我們可以調用 retryTemplate.execute() 方法:
retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
@Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});與其使用匿名類,我們可以使用 lambda 表達式:
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. 註冊監聽器
我們使用 withListener(new DefaultListenerSupport()) 方法,將我們的監聽器 DefaultListenerSupport 註冊到 RetryTemplate Bean 上,如前所述。
7. 驗證結果
為了完成我們的示例,讓我們驗證結果:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = AppConfig.class,
loader = AnnotationConfigContextLoader.class
)
public class SpringRetryIntegrationTest {
@Autowired
private MyService myService;
@Autowired
private RetryTemplate retryTemplate;
@Test
public void givenRetryService_whenCallWithException_thenRetry() {
assertThrows(RuntimeException.class, () -> myService.retryService());
}
}正如從示例測試日誌中可以看出,我們已正確配置了 RetryTemplate 和 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 - onClose8. 結論
在本文中,我們學習瞭如何使用 Spring Retry 通過註解、<em >RetryTemplate</em> 和回調監聽器進行配置。