Sisyphus
支持過程式編程和註解編程的 java 重試框架。
特性
-
支持 fluent 過程式編程
-
基於字節碼的代理重試
-
基於註解的重試,允許自定義註解
-
無縫接入 spring
-
接口與註解的統一
- 解決 spring-retry 與 guava-retrying 中的不足之處
設計目的
綜合了 spring-retry 和 gauva-retrying 的優勢。
調整一些特性,使其更利於實際使用。
採用 Netty 類似的接口思想,保證接口的一致性,和替換的靈活性。
借鑑 Hibernate-Validator 的設計,允許用戶自定義註解。
更新記錄
開源地址
快速開始
引入
<plugin>
<groupId>com.github.houbb</groupId>
<artifactId>sisyphus-core</artifactId>
<version>0.0.6</version>
</plugin>
入門代碼
詳情參見 [RetryerTest]()
public void helloTest() {
Retryer.<String>newInstance()
.retry(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("called...");
throw new RuntimeException();
}
});
}
代碼分析
- retry
指定一個 callable 的實現。
我們打印一條日誌,並且模擬一個程序異常。
日誌信息
日誌信息
called...
called...
called...
和一些其他異常信息。
重試觸發的條件,默認是程序發生了異常
這裏的重試間隔默認爲沒有時間間隔,一共嘗試3次。(包括第一次程序本身執行)
爲什麼選擇 sisyphus
爲什麼選擇 sisyphus
作爲開發者,我們一般都會選擇比較著名的框架。
比如 guava-retrying spring-retry。
或者乾脆自己寫一個。
爲什麼不是 guava-retrying/spring-retry
java retry 這篇文章中我列舉了常見的實現方式
以及上述的兩種框架,也講述了其中的不足。
guava-retrying 優缺點
優點
-
使用靈活
-
fluent 優雅寫法
- 提供足夠多的實現
缺點
-
沒有默認基於註解的實現
- 重試策略設計並不友好
spring-retry
優點
- 使用簡單
缺點
-
重試條件單一
-
重試等待策略單一
- 無法自定義註解
爲什麼不自己寫一個
個人感受
我作爲一名開發,平時說實在的,看到重試。
我肯定會偷懶寫一個 for 循環,重試幾次就結束了。
因爲時間不允許。
如果你更勤快一點,就可以選擇 spring-retry/guava-retrying。如果你熟悉他們的優缺點的話。
如果你渴望創造
sisyphus 所有的實現都是基於接口的。
你完全可以實現自己的實現,所有的東西基本完全可以被替換。
當然一些常見的策略實現,項目的基本框架都有詳盡的註釋,當做參考也可以有一點幫助。
sisyphus 做的更多的事情
netty 的靈感
參考了 netty 的設計,保證接口實現的一致性。
而且 sisyphus 還做了更多,還保證了接口和註解之間的一致性。
使用引導類,保證使用時的便利性,後期拓展的靈活性。
hibernate-validator
hibernate-validator 的作者是我知道爲數不多的對於 java 註解應用很棒的開發者。(雖然所知甚少)
自定義註解就是從這個框架中學來的。
與 spring 爲伍
spring 基本與我們的代碼形影不離,所以你可以很簡單的結合 spring.
就像你使用 spring-retry 一樣。
sisyphus 模塊簡介
模塊劃分
sisyphus 在模塊劃分的時候考慮到使用者的方便,主要有幾個模塊:
sisyphus-api
接口定義模塊,是最基礎的部分。
會被 sisyphus-core 默認依賴。
一般不需要引入,如果你想根據它實現自己的重試框架,不妨一試。
sisyphus-core
對於 sisyphus-api 模塊的默認實現。
並且添加易於使用的 Fluent 引導類,可以很方便的寫出聲明式的重試代碼。
sisyphus-annotation
sisyphus 的註解實現模塊。
(1)基於字節碼實現的代理重試,可以不依賴 spring。平時使用也更加靈活
(2)允許自定義註解及其實現。使用者可以編寫屬於自己的重試註解。
sisyphus-spring
spring 做爲 java 開發的引導者。自然是要支持的。
你可以和使用 spring-retry 一樣方便的使用 sisyphus-spring。
模塊間的依賴關係
sisyphus-api
sisyphus-core
sisyphus-annotation
sisyphus-spring
sisyphus-test
sisyphus-api 是基礎的,靈活性最高。
sisyphus-spring 是最簡單易用的,靈活性相對較差。
sisyphus-test 僅僅用作測試,不用外部引入。
sisyphus 配置概覽
爲了滿足更加方便的配置,Retryer 類提供了許多可以配置的信息。
默認配置
/**
* 默認配置測試
*/
public void defaultConfigTest() {
Retryer.<String>newInstance()
.condition(RetryConditions.hasExceptionCause())
.retryWaitContext(RetryWaiter.<String>retryWait(NoRetryWait.class).context())
.maxAttempt(3)
.listen(RetryListens.noListen())
.recover(Recovers.noRecover())
.callable(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("called...");
throw new RuntimeException();
}
}).retryCall();
}
和下面的代碼是等價的:
public void helloTest() {
Retryer.<String>newInstance()
.callable(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("called...");
throw new RuntimeException();
}
}).retryCall();
}
方法說明
condition
重試觸發的條件,可以指定多個條件。
默認爲拋出異常。
retryWaitContext
重試等待的策略,可以指定多個。
默認爲不做任何等待。
maxAttempt
指定最大重試次數,包括第一次執行。
默認值:3 次。
listen
指定重試的監聽實現,默認爲不做監聽。
recover
當重試完成之後,依然滿足重試條件,則可以指定恢復的策略。
默認不做恢復。
callable
待重試執行的方法。
retryCall
觸發重試執行。
接口的詳細介紹
接口及其實現
所有的接口,都可以直接查看對應的子類實例。
用戶自定義
基於替換的靈活性,用戶可以實現接口,定義更符合自己業務的實現。
sisyphus 註解
配置具有很高的靈活性,但是對於開發人員的使用,就沒有註解那樣簡單靈活。
所以本框架也實現了基於註解的重試。
設計的規範
保證接口和註解二者的統一性。
maven 引入
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sisyphus-annotation</artifactId>
<version>${project.version}</version>
</dependency>
註解
核心註解主要有兩個。
Retry
用於指定重試的相關配置。
/**
* 重試註解
* 1. 實際需要,只允許放在方法上。
* 2. 如果放在接口上,是否所有的子類都生效?爲了簡單明確,不提供這種實現。
* 3. 保持註解和接口的一致性。{@link com.github.houbb.sisyphus.api.core.Retry} 接口
* @author binbin.hou
* @since 0.0.3
*/
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@RetryAble(DefaultRetryAbleHandler.class)
public @interface Retry {
/**
* 重試類實現
* @return 重試
* @since 0.0.5
*/
Class<? extends com.github.houbb.sisyphus.api.core.Retry> retry() default DefaultRetry.class;
/**
* 最大嘗試次數
* 1. 包含方法第一次正常執行的次數
* @return 次數
*/
int maxAttempt() default 3;
/**
* 重試觸發的場景
* @return 重試觸發的場景
*/
Class<? extends RetryCondition> condition() default ExceptionCauseRetryCondition.class;
/**
* 監聽器
* 1. 默認不進行監聽
* @return 監聽器
*/
Class<? extends RetryListen> listen() default NoRetryListen.class;
/**
* 恢復操作
* 1. 默認不進行任何恢復操作
* @return 恢復操作對應的類
*/
Class<? extends Recover> recover() default NoRecover.class;
/**
* 等待策略
* 1. 支持指定多個,如果不指定,則不進行任何等待,
* @return 等待策略
*/
RetryWait[] waits() default {};
}
RetryWait
用於指定重試的等待策略。
package com.github.houbb.sisyphus.annotation.annotation;
import com.github.houbb.sisyphus.annotation.annotation.metadata.RetryWaitAble;
import com.github.houbb.sisyphus.annotation.handler.impl.DefaultRetryWaitAbleHandler;
import com.github.houbb.sisyphus.core.constant.RetryWaitConst;
import com.github.houbb.sisyphus.core.support.wait.NoRetryWait;
import java.lang.annotation.*;
/**
* 重試等待策略
* 1. 爲了對應重試策略,所有的內置註解應該實現當前的註解。
* 2. 是否允許自定義註解?
*
* 當註解+對象同時出現的時候,視爲組合。
*
* @author binbin.hou
* @since 0.0.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Target(ElementType.ANNOTATION_TYPE)
@RetryWaitAble(DefaultRetryWaitAbleHandler.class)
public @interface RetryWait {
/**
* 默認值
* 1. fixed 模式,則對應固定等待時間
* 2. 遞增
* @return 默認值
*/
long value() default RetryWaitConst.VALUE_MILLS;
/**
* 最小值
* @return 最小值
*/
long min() default RetryWaitConst.MIN_MILLS;
/**
* 最大值
* @return 最大值
*/
long max() default RetryWaitConst.MAX_MILLS;
/**
* 影響因數
* 1. 遞增重試,默認爲 {@link RetryWaitConst#INCREASE_MILLS_FACTOR}
* 2. 指數模式。默認爲 {@link RetryWaitConst#MULTIPLY_FACTOR}
* @return 影響因數
*/
double factor() default Double.MIN_VALUE;
/**
* 指定重試的等待時間 class 信息
* @return 重試等待時間 class
*/
Class<? extends com.github.houbb.sisyphus.api.support.wait.RetryWait> retryWait() default NoRetryWait.class;
}
註解的使用
定義好了註解,肯定要有註解的相關使用。
關於註解的使用,主要有兩種方式。
Proxy+CGLIB
基於代理模式和字節碼增強。
如果是項目中沒有使用 spring,直接使用這種方式比較方便。
Spring-AOP
可以和 spring 直接整合。
使用方式和 spring-retry 是一樣的。
sisyphus 代理模板
目的
爲了便於用戶更加方便地使用註解,同時又不依賴 spring。
提供基於代碼模式+字節碼增強實現的方式。
使用案例
maven 引入
引入註解相關模塊。
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sisyphus-annotation</artifactId>
<version>${project.version}</version>
</dependency>
定義測試方法
以下測試代碼可以參考 [spring-test]() 模塊。
- MenuServiceImpl.java
public class MenuServiceImpl {
public void queryMenu(long id) {
System.out.println("查詢菜單...");
throw new RuntimeException();
}
@Retry
public void queryMenuRetry(long id) {
System.out.println("查詢菜單...");
throw new RuntimeException();
}
}
測試
使用 RetryTemplate 進行測試
無重試註解的方法
@Test(expected = RuntimeException.class)
public void templateTest() {
MenuServiceImpl menuService = RetryTemplate.getProxyObject(new MenuServiceImpl());
menuService.queryMenu(1);
}
- 日誌信息
查詢菜單...
只請求了一次。
有註解的方法
@Test(expected = RuntimeException.class)
public void templateRetryTest() {
MenuServiceImpl menuService = RetryTemplate.getProxyObject(new MenuServiceImpl());
menuService.queryMenuRetry(1);
}
- 日誌信息
查詢菜單...
查詢菜單...
查詢菜單...
其他
當然還有更多的配置,可以自行嘗試。
如果你想結合 spring 使用註解,請繼續往下看。
sisyphus spring 整合
目的
類似於 spring-retry 框架,如果你使用 spring 框架,那麼整合本項目將會非常簡單。
註解的方式和過程式編程,二者儘可能的保持一致性,你想從一種方式變爲另一種也比較簡單。
想從 spring-retry 切換到本框架也很方便。
使用示例
maven 引入
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sisyphus-spring</artifactId>
<version>${project.version}</version>
</dependency>
會默認引入 spring 以及 AOP 相關 jar。
業務代碼
你可以參考 sisyphus-test 模塊。
下面模擬非常常見的一些業務方法。
使用 @Retry
標識方法需要進行重試。
- SpringService.java
public interface SpringService {
/**
* 查詢示例代碼
* @return 結果
*/
String query();
}
- SpringServiceImpl.java
import com.github.houbb.sisyphus.annotation.annotation.Retry;
import com.github.houbb.sisyphus.test.service.SpringService;
import org.springframework.stereotype.Service;
/**
* @author binbin.hou
* @since 0.0.4
*/
@Service
public class SpringServiceImpl implements SpringService {
@Override
@Retry
public String query() {
System.out.println("spring service query...");
throw new RuntimeException();
}
}
開啓重試
基於註解直接如下配置即可。
使用 @EnableRetry
標識需要開啓重試。
@Configurable
@ComponentScan(basePackages = "com.github.houbb.sisyphus.test.service")
@EnableRetry
public class SpringConfig {
}
測試代碼
import com.github.houbb.sisyphus.test.config.SpringConfig;
import com.github.houbb.sisyphus.test.service.SpringService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4Cla***unner;
/**
* @author binbin.hou
* @since 0.0.4
*/
@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4Cla***unner.class)
public class SpringServiceTest {
@Autowired
private SpringService springService;
@Test(expected = RuntimeException.class)
public void queryTest() {
springService.query();
}
}
- 日誌信息
spring service query...
spring service query...
spring service query...
新特性預定
入參
重試上下文添加入參信息
配置優化
提供更加優異的配置體驗