自定义方法重试注解

作者: 其实我做事不过脑的 | 来源:发表于2016-04-20 02:18 被阅读513次

概述

当你的系统依赖外部服务时,总是会有那么一些方法在执行时有可能会失败。方法执行失败了,那自然是再调用一次试试,说不定就调通了呢。为每个方法编写重试代码显然是对程序员的折磨,故下面我们介绍一个清爽的方法重试方案,其最终效果是被标注为@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();
   }
}
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();
   }
}
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)

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

相关文章

  • 自定义方法重试注解

    概述 当你的系统依赖外部服务时,总是会有那么一些方法在执行时有可能会失败。方法执行失败了,那自然是再调用一次试试,...

  • 注解学习

    自定义注解 方法参数的注解 方法的注解 注解获取解析 结果 http://springforall.ufile.u...

  • Spring Aop 注解式声明记录用户操作日志

    一、自定义注解(annotation) 自定义注解的作用:在反射中获取注解,以取得注解修饰的类、方法或属性的相关解...

  • 大连滕泰科技学习笔记2020-06-12

    1,注解 如何自定义注解?自定义注解应该遵循那些规则?基本类型String枚举Class 属性方法的名词publi...

  • 自定义redis缓存注解

    1、自定义缓存注解使用示例 2、自定注解,作用于方法上 3、自定义注解,作用于参数上 4、缓存注解拦截器

  • Spring自定义注解的使用和解析

    自定义的注解 ,可通过Spring快速的获取所有使用该注解的类或方法或属性,以及注解内的值。 自定义一个注解 定义...

  • 登录拦截器

    定义注解和拦截器 自定义注解,对标记了注解的方法进行拦截 定义拦截器,根据注解进行方法拦截 配置拦截器 使用

  • 利用注解进行方法测试

    利用注解进行方法测试 可以通过自定义一个注解方法,在需要进行测试的类的方法中使用注解来实现指定方法的执行测试。 1...

  • 46_springboot的自定义注解简单使用

    通过自定义注解,可以轻松在方法或者类执行时去前置执行各种想要的方法 先导入pom坐标 1.先定义自定义注解 2.准...

  • Java注解笔记

    首先看下列的一个简单自定义注解的例子:Table 注解 Column 注解 User 类(使用注解的类) 测试方法...

网友评论

    本文标题:自定义方法重试注解

    本文链接:https://www.haomeiwen.com/subject/ikyzlttx.html