自定義方法重試注解

概述

當(dāng)你的系統(tǒng)依賴外部服務(wù)時(shí)号阿,總是會(huì)有那么一些方法在執(zhí)行時(shí)有可能會(huì)失敗并鸵。方法執(zhí)行失敗了,那自然是再調(diào)用一次試試倦西,說(shuō)不定就調(diào)通了呢能真。為每個(gè)方法編寫重試代碼顯然是對(duì)程序員的折磨,故下面我們介紹一個(gè)清爽的方法重試方案扰柠,其最終效果是被標(biāo)注為@RetryExecution的方法,如果在運(yùn)行過(guò)程中拋了異常疼约,其會(huì)被再次嘗試運(yùn)行若干次卤档。

(相同的思路,還可以實(shí)現(xiàn)自定義事物程剥、鎖劝枣、時(shí)間統(tǒng)計(jì)、異步重試等很多功能)

Spring AOP Proxies

關(guān)于Spring AOP的詳細(xì)介紹參見(jiàn)官方文檔织鲸,這里我們只介紹AOP Proxies的核心思想舔腾。考慮如下場(chǎng)景:

public class SimplePojo implements Pojo {

   public void foo() {
      // this next method invocation is a direct
      call on the 'this' reference
      this.bar();
   }

   public void bar() {
      // some logic...
   }
}
public class Main {

   public static void main(String[] args) {

      Pojo pojo = new SimplePojo();

      // this is a direct method call on the 'pojo' reference
      pojo.foo();
   }
}
aop_proxy_plain_pojo_call

當(dāng)我們創(chuàng)建一個(gè)對(duì)象的實(shí)例搂擦,通過(guò)其實(shí)例的引用調(diào)用其方法時(shí)稳诚,我們直接調(diào)用了對(duì)象的方法。倘若我們想在方法執(zhí)行前后做一些額外的工作瀑踢,但是不侵入方法的代碼扳还,一個(gè)自然的想法就是為對(duì)象創(chuàng)建代理才避。

public class Main {

   public static void main(String[] args) {

      ProxyFactory factory = new ProxyFactory(new SimplePojo());
      factory.addInterface(Pojo.class);
      factory.addAdvice(new RetryAdvice());

      Pojo pojo = (Pojo) factory.getProxy();

      // this is a method call on the proxy!
      pojo.foo();
   }
}
aop_proxy_call

我們?yōu)閷?duì)象創(chuàng)建了動(dòng)態(tài)代理,對(duì)象的方法實(shí)際上由代理負(fù)責(zé)執(zhí)行氨距,由此我們得以通過(guò)定義代理的行為桑逝,在不侵入原方法的代碼的情況下擴(kuò)展方法的執(zhí)行行為(e.g. 事物、計(jì)時(shí)俏让、重試楞遏、etc.)。

實(shí)現(xiàn)方案

基于如上原理首昔,我們接下來(lái)通過(guò)擴(kuò)展AOP API來(lái)實(shí)現(xiàn)清爽的方法重試方案橱健。

定義重試注解

/**
 * Created by benxue on 3/24/16.
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RetryExecution {
    int retryTimes() default 1;
}

定義攔截器

這里是被標(biāo)注的方法實(shí)際執(zhí)行的地方。

/**
 * Created by benxue on 3/24/16.
 */
@Component
public class RetryMethodInterceptor implements MethodInterceptor {

    private static final Logger logger = LogManager.getLogger(RetryMethodInterceptor.class.getName());

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

       //cglib代理和jvm代理在獲取annotation時(shí)是不是有區(qū)別沙廉?
        int retryTimes = invocation.getMethod().getAnnotation(RetryExecution.class).retryTimes();

        while (--retryTimes >= 0) {
            try {
                return invocation.proceed();
            } catch (Throwable t) {
                logger.error(t);
            }
        }
        return invocation.proceed();
    }
}

定義Advisor

Spring容器初始化Bean時(shí)拘荡,通過(guò)Advisor的Pointcut對(duì)象判斷Bean中的方法是否需要被特殊處理(我們的例子中通過(guò)注解類型判斷,同時(shí)判斷結(jié)果會(huì)緩存下來(lái))撬陵。如果需要珊皿,當(dāng)方法通過(guò)代理被調(diào)用時(shí),其會(huì)被轉(zhuǎn)給Advisor的getAdvice方法返回的攔截器執(zhí)行巨税。

/**
 * Created by benxue on 3/24/16.
 */
@Component
public class RetryMethodAdvisor extends AbstractPointcutAdvisor {

    private final StaticMethodMatcherPointcut pointcut = new
            StaticMethodMatcherPointcut() {
                @Override
                public boolean matches(Method method, Class<?> targetClass) {
                    return method.isAnnotationPresent(RetryExecution.class);
                }
            };

    @Autowired
    private RetryMethodInterceptor interceptor;

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    public Advice getAdvice() {
        return this.interceptor;
    }
}

全局配置

下面的配置會(huì)使得Spring在初始化時(shí)蟋定,尋找全部存在的的Advisor,并嘗試通過(guò)識(shí)別出的Advisor為所有存在的Bean的方法配置攔截器草添。(其實(shí)我們強(qiáng)制使用了cglib)

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
    <property name="proxyTargetClass" value="true"/>
</bean>

<aop:aspectj-autoproxy proxy-target-class="true"/>

使用示例 && Self Injection

/**
 * Created by benxue on 3/16/16.
 */
@Service
public class ServiceImpl implements Service {

    ServiceImpl self;

    @Autowired
    private ApplicationContext applicationContext;

    // 在Service初始化完成之后驶兜,注入其Reference
    @PostConstruct
    private void init() {
        self = applicationContext.getBean("ServiceImpl", ServiceImpl.class);
    }
    
    @Override
    public void hello(){
        self.hahaha();
    }
    
    @Override
    public void hi(){
        hahaha();
    }
    
    @RetryExecution
    @Override
    public void hahaha() throw RuntimeException(){...}
}
public class Test {

    @Autowired
    private Service service;

    private void test() {
        service.hello();// 如果拋出異常,hahaha會(huì)被重新執(zhí)行一次
        service.hi(); // hahaha被本地調(diào)用远寸,無(wú)論是否拋出異常抄淑,hahaha都不會(huì)被重試
        service.hahaha(); // 如果拋出異常,hahaha會(huì)被重新執(zhí)行一次
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驰后,一起剝皮案震驚了整個(gè)濱河市肆资,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灶芝,老刑警劉巖郑原,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異夜涕,居然都是意外死亡犯犁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門女器,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酸役,“玉大人,你說(shuō)我怎么就攤上這事〈睾矗” “怎么了只壳?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)暑塑。 經(jīng)常有香客問(wèn)我吼句,道長(zhǎng),這世上最難降的妖魔是什么事格? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任惕艳,我火速辦了婚禮,結(jié)果婚禮上驹愚,老公的妹妹穿的比我還像新娘远搪。我一直安慰自己,他們只是感情好逢捺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布谁鳍。 她就那樣靜靜地躺著,像睡著了一般劫瞳。 火紅的嫁衣襯著肌膚如雪倘潜。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天志于,我揣著相機(jī)與錄音涮因,去河邊找鬼。 笑死伺绽,一個(gè)胖子當(dāng)著我的面吹牛养泡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奈应,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼澜掩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了钥组?” 一聲冷哼從身側(cè)響起输硝,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎程梦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體橘荠,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屿附,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哥童。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挺份。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贮懈,靈堂內(nèi)的尸體忽然破棺而出匀泊,到底是詐尸還是另有隱情优训,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布各聘,位于F島的核電站揣非,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏躲因。R本人自食惡果不足惜早敬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望大脉。 院中可真熱鬧搞监,春花似錦、人聲如沸镰矿。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秤标。三九已至绝淡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抛杨,已是汗流浹背够委。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怖现,地道東北人茁帽。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像屈嗤,于是被迫代替她去往敵國(guó)和親潘拨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理饶号,服務(wù)發(fā)現(xiàn)铁追,斷路器,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • 什么是Spring Spring是一個(gè)開(kāi)源的Java EE開(kāi)發(fā)框架茫船。Spring框架的核心功能可以應(yīng)用在任何Jav...
    jemmm閱讀 16,462評(píng)論 1 133
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,810評(píng)論 6 342
  • 從三月份找實(shí)習(xí)到現(xiàn)在琅束,面了一些公司,掛了不少算谈,但最終還是拿到小米涩禀、百度、阿里然眼、京東艾船、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,246評(píng)論 11 349
  • 來(lái)屿岂,讓我們一起談?wù)撐覀兊撵`魂 甚至避開(kāi)自己的耳目 就像一座玫瑰園 永遠(yuǎn)保持著微笑 就像想象力無(wú)聲的訴說(shuō) 像精神統(tǒng)治...
    詠鵝閱讀 211評(píng)論 0 0