概述
當(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();
}
}
當(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();
}
}
我們?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í)行一次
}
}