自定义方法重试注解

概述

当你的系统依赖外部服务时,总是会有那么一些方法在执行时有可能会失败。方法执行失败了,那自然是再调用一次试试,说不定就调通了呢。为每个方法编写重试代码显然是对程序员的折磨,故下面我们介绍一个清爽的方法重试方案,其最终效果是被标注为@RetryExecution的方法,如果在运行过程中抛了异常,其会被再次尝试运行若干次。

(相同的思路,还可以实现自定义事物、锁、时间统计、异步重试等很多功能)

Spring AOP Proxies

关于Spring AOP的详细介绍参见官方文档,这里我们只介绍AOP Proxies的核心思想。考虑如下场景:

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();
   }
}
自定义方法重试注解_第1张图片
aop_proxy_plain_pojo_call

当我们创建一个对象的实例,通过其实例的引用调用其方法时,我们直接调用了对象的方法。倘若我们想在方法执行前后做一些额外的工作,但是不侵入方法的代码,一个自然的想法就是为对象创建代理。

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();
   }
}
自定义方法重试注解_第2张图片
aop_proxy_call

我们为对象创建了动态代理,对象的方法实际上由代理负责执行,由此我们得以通过定义代理的行为,在不侵入原方法的代码的情况下扩展方法的执行行为(e.g. 事物、计时、重试、etc.)。

实现方案

基于如上原理,我们接下来通过扩展AOP API来实现清爽的方法重试方案。

定义重试注解

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

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

定义拦截器

这里是被标注的方法实际执行的地方。

/**
 * 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时是不是有区别?
        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时,通过Advisor的Pointcut对象判断Bean中的方法是否需要被特殊处理(我们的例子中通过注解类型判断,同时判断结果会缓存下来)。如果需要,当方法通过代理被调用时,其会被转给Advisor的getAdvice方法返回的拦截器执行。

/**
 * 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;
    }
}

全局配置

下面的配置会使得Spring在初始化时,寻找全部存在的的Advisor,并尝试通过识别出的Advisor为所有存在的Bean的方法配置拦截器。(其实我们强制使用了cglib)


    




使用示例 && 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会被重新执行一次
        service.hi(); // hahaha被本地调用,无论是否抛出异常,hahaha都不会被重试
        service.hahaha(); // 如果抛出异常,hahaha会被重新执行一次
    }
}

你可能感兴趣的:(自定义方法重试注解)