自定義注解實(shí)現(xiàn)方法重試

自定義注解實(shí)現(xiàn)方法重試

其實(shí)重試機(jī)制就是一個(gè)方法或者接口 調(diào)一次沒有達(dá)到預(yù)想的期望扑媚,接著對(duì)此進(jìn)行重新執(zhí)行源织,直到重試次數(shù)用完為止

JAVA實(shí)現(xiàn)重試機(jī)制可以用模版方式售淡、切面方式、消息總線方式

設(shè)計(jì):

目標(biāo)是實(shí)現(xiàn)一個(gè)優(yōu)雅的重試機(jī)制朵栖。首先應(yīng)該是

  • 無侵入:不改動(dòng)當(dāng)前的業(yè)務(wù)邏輯,對(duì)于需要重試的方法或接口可以簡單的實(shí)現(xiàn)

  • 可配置的:重試次數(shù)羹与、重試間隔時(shí)間娩嚼、是否使用異步方式)

  • 通用性:最好是無改動(dòng)可支持絕大部分場景

一般希望做到無侵入,都采用的切面或者消息總線模式腐泻,可配置和通用性則比較清晰决乎,基本上開始做就表示這兩點(diǎn)是基礎(chǔ)要求,唯一的要求就是不要硬編碼派桩,不能寫死构诚,基本上就能達(dá)到這一基礎(chǔ)要求。所以要做的并不少

切面方式

這個(gè)思路比較清晰铆惑,在需要添加重試的方法上添加一個(gè)用于重試的自定義注解范嘱,然后在切面中實(shí)現(xiàn)重試的邏輯,主要的配置參數(shù)則根據(jù)注解的選項(xiàng)來初始化员魏。也可以不傳參丑蛤,在對(duì)重試次數(shù)和間隔時(shí)間進(jìn)行定義時(shí)做成可遠(yuǎn)程配置化

優(yōu)點(diǎn):真正的無侵入

缺點(diǎn):某些方法無法被切面攔截的場景無法覆蓋(如spring-aop無法切私有方法,final方法)

        直接使用aspectj則有些小復(fù)雜撕阎;如果用spring-aop受裹,則只能切被spring容器管理的bean

我們這里用的是比較常用的一個(gè) AOP 切面方式實(shí)現(xiàn)

1. 先定義一個(gè)重試接口

package com.example.demo.util.retry;
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryDot {

    /**
     * 重試次數(shù)
     * @return
     */
    int count() default 0;

    /**
     * 重試的間隔時(shí)間
     * @return
     */
    int sleep() default 0;

    /**
     * 是否支持異步重試方式
     * @return
     */
    boolean asyn() default false;

}

2. 定義AOP切面對(duì)加注解的方法

package com.example.demo.util.retry;
import com.example.demo.dto.vo.ResultData;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * @ClassName RetryAspect
 * @Description TODO
 * @Date 2021/8/13 2:27 下午
 * @Created by liyanyan
 */
@Aspect
@Component
@Slf4j
public class RetryAspect {

    ExecutorService executorService = new ThreadPoolExecutor(3, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>());

    @Around(value = "@annotation(retryDot)")
    public Object execute(ProceedingJoinPoint joinPoint, RetryDot retryDot) throws Throwable {
        RetryTemplate retryTemplate = new RetryTemplate() {
            @Override
            protected Object doBiz() throws Throwable {
                System.out.println("重試......");
                ResultData result = (ResultData) joinPoint.proceed();
                return result;
            }
        };
        //這里進(jìn)行次數(shù)和間隔時(shí)間的注入,注解不傳參也可以自行做遠(yuǎn)程配置 統(tǒng)一控制
        retryTemplate.setRetryTime(retryDot.count())
                .setSleepTime(retryDot.sleep());

        if(retryDot.asyn()) {
            return retryTemplate.submit(executorService);
        }else {
            return retryTemplate.execute();
        }
    }

}

3. 模版方法 主要對(duì)重試邏輯判斷

package com.example.demo.util.retry;
import com.example.demo.dto.vo.ResultData;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
/**
 * @ClassName RetryTemplate
 * @Description TODO
 * @Date 2021/8/13 11:39 上午
 * @Created by liyanyan
 */
@Slf4j
public abstract class RetryTemplate {

    private static final int DEFAULT_RETRY_TIME = 1;
    private int retryTime = DEFAULT_RETRY_TIME;

    //重試的睡眠時(shí)間
    private int sleepTime = 0;

    public int getSleepTime() {
        return sleepTime;
    }

    public RetryTemplate setSleepTime(int sleepTime) {
        if(sleepTime < 0) {
            throw new IllegalArgumentException("sleepTime should equal or bigger than 0");
        }
        this.sleepTime = sleepTime;
        return this;
    }
    public int getRetryTime() {
        return retryTime;
    }
    public RetryTemplate setRetryTime(int retryTime) {
        if(retryTime <= 0) {
            throw new IllegalArgumentException("retryTime should bigger than 0");
        }
        this.retryTime = retryTime;
        return this;
    }
    /**
     * 重試的業(yè)務(wù)執(zhí)行代碼
     * 失敗時(shí)請(qǐng)拋出一個(gè)異常
     *
     * todo 去定返回的封裝類虏束,根據(jù)返回結(jié)果的狀態(tài)來判定是否需要重試 這里定義的是ResultData
     * @return
     * @throws Exception
     */
    protected abstract Object doBiz() throws Throwable;

    public Object execute() throws Throwable {
        ResultData result;
        for(int i=0; i<retryTime; i++) {
            result = (ResultData) doBiz();
            if(result.getCode()==200) {
                return doBiz();
            }
            Thread.sleep(sleepTime);
        }
        result = (ResultData) doBiz();
        return result;
    }

    public Object submit(ExecutorService executorService) {
        if(executorService == null) {
            throw new IllegalArgumentException("please choose executorService!");
        }

        return executorService.submit((Callable<? extends Object>) () -> {
            try {
                return execute();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            throw new RuntimeException("異步出大問題咯");
        });
    }
}

消息總線方式

這個(gè)也是比較容易理解棉饶,在需要重試的方法中,發(fā)送一個(gè)消息镇匀,并將業(yè)務(wù)邏輯作為回調(diào)方法傳入照藻;由一個(gè)訂閱了重試消息的consumer來執(zhí)行重試的業(yè)務(wù)邏輯

優(yōu)點(diǎn):重試機(jī)制不受任何限制,即在任何地方你都可以使用

        利用EventBus 框架汗侵,都可以非常容易把框架搭起來

缺點(diǎn):業(yè)務(wù)侵入岩梳,需要在重試的業(yè)務(wù)處囊骤,主動(dòng)發(fā)起一條重試消息

        調(diào)試?yán)斫鈴?fù)雜(消息總線方式的最大優(yōu)點(diǎn)和缺點(diǎn),就是過于靈活了冀值,你可能都不知道什么地方處理這個(gè)消息也物,特別是新人維護(hù)這段代碼 很頭疼)

        如果要獲取返回結(jié)果,不太好處理列疗,上下文參數(shù)不好處理

有興趣的可以了解一下如何實(shí)現(xiàn)滑蚯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抵栈,隨后出現(xiàn)的幾起案子告材,更是在濱河造成了極大的恐慌,老刑警劉巖古劲,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斥赋,死亡現(xiàn)場離奇詭異,居然都是意外死亡产艾,警方通過查閱死者的電腦和手機(jī)疤剑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闷堡,“玉大人隘膘,你說我怎么就攤上這事「芾溃” “怎么了弯菊?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長踱阿。 經(jīng)常有香客問我管钳,道長,這世上最難降的妖魔是什么软舌? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任蹋嵌,我火速辦了婚禮,結(jié)果婚禮上葫隙,老公的妹妹穿的比我還像新娘栽烂。我一直安慰自己,他們只是感情好恋脚,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布腺办。 她就那樣靜靜地躺著,像睡著了一般糟描。 火紅的嫁衣襯著肌膚如雪怀喉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天船响,我揣著相機(jī)與錄音躬拢,去河邊找鬼躲履。 笑死,一個(gè)胖子當(dāng)著我的面吹牛聊闯,可吹牛的內(nèi)容都是我干的工猜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼菱蔬,長吁一口氣:“原來是場噩夢啊……” “哼篷帅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拴泌,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤魏身,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蚪腐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箭昵,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年回季,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了家制。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茧跋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卓囚,到底是詐尸還是另有隱情瘾杭,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布哪亿,位于F島的核電站粥烁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝇棉。R本人自食惡果不足惜讨阻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望篡殷。 院中可真熱鬧钝吮,春花似錦、人聲如沸板辽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劲弦。三九已至耳标,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邑跪,已是汗流浹背次坡。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工呼猪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人砸琅。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓宋距,卻偏偏與公主長得像,于是被迫代替她去往敵國和親明棍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乡革,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345