spring Retryable注解实现重试详解

spring-boot:1.5.3.RELEASE,spring-retry-1.2.0.RELEASE

使用方法

引入pom

// 版本号继承spring-boot依赖管理的pom
<dependency>
 <groupId>org.springframework.retry</groupId>
 <artifactId>spring-retry</artifactId>
</dependency>
<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
</dependency>

启用重试

@Configuration
@ImportResource(locations = { "classpath*:spring/app-context-*" })
@EnableRetry
public class AppContext {
}

注解需要重试的方法

@Retryable(value = RuntimeException.class, maxAttempts = 3,backoff = @Backoff(delay = 10L, multiplier = 1))
public boolean myRetryableMethod(){
  ...
}

注解属性含义

Retryable

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

 /**
 * 为重试方法应用重试拦截器的bean名称。与其他属性互斥
 */
 String interceptor() default "";

 /**
 * 可以重试的异常类型。与includes属性同义。默认值为空(并且如果exclude也是空的话,
   * 所有的异常都会重试)
 */
 Class<? extends Throwable>[] value() default {};

 /**
 * 同上
 */
 Class<? extends Throwable>[] include() default {};

 /**
 * 与include含义相反
 */
 Class<? extends Throwable>[] exclude() default {};

 /**
 * 统计报告的唯一标签。如果没有提供,调用者可以选择忽略它,或者提供一个默认值。
 *
 * @return the label for the statistics
 */
 String label() default "";

 /**
 * 标识重试有状态的:即异常重新抛出,但是重试策略使用相同的策略应用于后续的具有相同参数的
   * 调用。如果为false那么可重试的异常不会重新抛出。
 */
 boolean stateful() default false;

 /**
   * 尝试的最大次数(包含第一次失败),默认为3
 */
 int maxAttempts() default 3;

 /**
 * 返回一个求尝试最大次数值的表达式(包含第一次失败),默认为3
   * 重写 {@link #maxAttempts()}。
 * @since 1.2
 */
 String maxAttemptsExpression() default "";

 /**
 * 为正重试的动作指定backoff属性。默认没有backoff,但是在两次尝试之间暂定一下是一个很好的想法
 * (即使代价是阻塞线程)
 */
 Backoff backoff() default @Backoff();

 /**
 * 在{@code SimpleRetryPolicy.canRetry()}返回true之后指定一个计算表达式 - 可用来有条件的取消重试。
 * 仅在调用抛出一个异常后。求值的root对象为上一次的异常 {@code Throwable}。
 * 可以引用上下文中的其他beans。
 * 例如:
 * {@code "message.contains('you can retry this')"}.
 * and
 * {@code "@someBean.shouldRetry(#root)"}.
 * @since 1.2
 */
 String exceptionExpression() default "";
}

Backoff

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Backoff {

 /**
 * 与 {@link #delay()} 属性同义
 *
 * 返回延迟多少毫秒后重试(默认为1000毫秒)
 */
 long value() default 1000;

 /**
 * 一个标准的再重试周期。在指数函数情况下用作初始值,在始终如一的情况下(固定周期值情况)
 * 用作最小值。
 * @return the initial or canonical backoff period in milliseconds (default 1000)???
 */
 long delay() default 0;

 /**
 * 重试之间最大等待(毫秒)时间。如果小于 {@link #delay()} 则忽略。
 * @return the maximum delay between retries (default 0 = ignored)
 */
 long maxDelay() default 0;

 /**
 * 如果是正数,则用于生成下次再重试等待时间的乘数。
 * 返回一个乘数用于计算下次再重试延迟(默认为0忽略)
 */
 double multiplier() default 0;

 /**
 * 标准再重试周期求值表达式。在指数情况下用作初始值,始终如一的情况下用作最小值。
 * 重写 {@link #delay()}.
 * @since 1.2
 */
 String delayExpression() default "";

 /**
 * 在重试之间最大等待(毫秒)数的求值表达式。
 * 如果小于 {@link #delay()} 则忽略。
 * 重写 {@link #maxDelay()}
 * 默认为0,忽略
 * @since 1.2
 */
 String maxDelayExpression() default "";

 /**
 * 表达式求值作为生成下次再重试延迟的乘数
 * 重写 {@link #multiplier()}。
 * @since 1.2
 */
 String multiplierExpression() default "";

 /**
 * 在指数情况下 ({@link #multiplier()} > 0) 设置该值为true将使再重试延迟随机化,
 * 使最大延迟为先前延迟的乘数倍数,并使这两个延迟值之间分布均匀。
 * 默认为false
 */
 boolean random() default false;
}

案例

默认retry

@Component
public class MyTask {

  @Retryable
  public void doExecute(){
    System.out.println("## current Date:" + new Date());
    throw new RuntimeException("my test");
  }
}

输出结果

## current Date:Sat Aug 29 21:54:55 CST 2020 ## current Date:Sat Aug 29 21:54:56 CST 2020 ## current Date:Sat Aug 29 21:54:57 CST 2020 2020-08-29 21:55:00,319 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test ...

stateful

源码相同,注解增加属性配置

@Retryable( stateful = true )

public void doExecute(){

输出结果

## current Date:Sat Aug 29 21:58:56 CST 2020 2020-08-29 21:58:57,557 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test // 没有重新抛出异常触发重试

该参数为false时会重试3次后抛出异常,重试期间不会重新抛出异常。参数为true时则重试期间也会重新抛出异常导致重试失败不再继续重试

backoff.multiplier

注解属性配置

@Retryable( backoff = @Backoff( delay = 1000, multiplier = 2), maxAttempts = 10)

输出结果

## current Date:Sat Aug 29 23:06:50 CST 2020 ## current Date:Sat Aug 29 23:06:51 CST 2020 ## current Date:Sat Aug 29 23:06:53 CST 2020 ## current Date:Sat Aug 29 23:06:57 CST 2020 ## current Date:Sat Aug 29 23:07:05 CST 2020 ## current Date:Sat Aug 29 23:07:21 CST 2020 ## current Date:Sat Aug 29 23:07:51 CST 2020 ## current Date:Sat Aug 29 23:08:21 CST 2020 ## current Date:Sat Aug 29 23:08:51 CST 2020 ## current Date:Sat Aug 29 23:09:21 CST 2020 2020-08-29 23:09:21,949 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test

乘数正确,指数型增长,第1次延迟1s

第2次,上次延迟1s乘以乘数2=延迟2s

第3次,上次延迟2s乘以乘数2=延迟4s

指数增长,如果没有指定则为始终如一的固定间隔延迟类型。新版本已经增加了各种类型单独的属性配置的模板构建者:

RetryTemplate.builder()
   .maxAttempts(10)
   .exponentialBackoff(100, 2, 10000)
   .retryOn(IOException.class)
   .traversingCauses()
   .build();

RetryTemplate.builder()
   .fixedBackoff(10)
   .withinMillis(3000)
   .build();

RetryTemplate.builder()
   .infiniteRetry()
   .retryOn(IOException.class)
   .uniformRandomBackoff(1000, 3000)
   .build();

backoff.random

测试代码

@Component
public class MyTask {

  private Long lastTime = null;

  @Retryable( backoff = @Backoff( delay = 1000, multiplier = 2, random = true), maxAttempts = 10)
  public void doExecute(){
    if (lastTime == null) {
      lastTime = System.currentTimeMillis();
    }
    System.out.println("## actual delay:" + (System.currentTimeMillis() - lastTime) );
    RuntimeException runtimeException = new RuntimeException("my test");
    throw runtimeException;
  }
}

输出结果

## current Date:Sat Aug 29 22:53:10 CST 2020
## current Date:Sat Aug 29 22:53:11 CST 2020
## current Date:Sat Aug 29 22:53:14 CST 2020
## current Date:Sat Aug 29 22:53:20 CST 2020
## current Date:Sat Aug 29 22:53:29 CST 2020
## current Date:Sat Aug 29 22:53:51 CST 2020
## current Date:Sat Aug 29 22:54:41 CST 2020
## current Date:Sat Aug 29 22:55:25 CST 2020
## current Date:Sat Aug 29 22:56:11 CST 2020
## current Date:Sat Aug 29 22:57:01 CST 2020
2020-08-29 22:57:01,617 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection
Exception in thread "main" java.lang.RuntimeException: my test

延迟更加随机化,由于是最大延迟为之前延迟的乘数的倍数,所以看不出规律。它的使用场景是使延迟更加随机化

exceptionExpression

测试代码

@Component
public class MyTask {

  private Long lastTime = null;

  public boolean canRetry(RuntimeException runtimeException) {
    System.out.println("canRetry:"+runtimeException.hashCode());
    return true;
  }

  @Retryable(exceptionExpression = "#{@myTask.canRetry(#root)}", backoff = @Backoff(delay = 1000, multiplier = 2, random = true))
  public void doExecute() {
    if (lastTime == null) {
      lastTime = System.currentTimeMillis();
    }
    System.out.println("## actual delay:" + (System.currentTimeMillis() - lastTime));
    RuntimeException runtimeException = new RuntimeException("my test");
    System.out.println("doExecute:"+runtimeException.hashCode());
    throw runtimeException;
  }
}

输出结果

## actual delay:0 doExecute:626562869 2020-08-29 23:50:49,905 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:626562869 2020-08-29 23:50:49,906 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection 2020-08-29 23:50:51,335 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:626562869 2020-08-29 23:50:51,336 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection ## actual delay:1450 doExecute:90418597 2020-08-29 23:50:51,337 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:90418597 2020-08-29 23:50:51,338 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection 2020-08-29 23:50:53,620 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:90418597 2020-08-29 23:50:53,620 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection ## actual delay:3734 doExecute:307531674 2020-08-29 23:50:53,621 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test

注意:1.2.5之后表达式的预发有所改变,详情可以参考官方文档:https://github.com/spring-projects/spring-retry

以上这篇spring Retryable注解实现重试详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring的异常重试框架Spring Retry简单配置操作

    相关api见:点击进入 /* * Copyright 2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *

  • Spring Cloud重试机制与各组件的重试总结

    SpringCloud重试机制配置 首先声明一点,这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例. @Bean @LoadBalanced RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); httpRequestFactory.se

  • SpringBoot @Retryable注解方式

    背景 在调用第三方接口或者使用MQ时,会出现网络抖动,连接超时等网络异常,所以需要重试.为了使处理更加健壮并且不太容易出现故障,后续的尝试操作,有时候会帮助失败的操作最后执行成功.一般情况下,需要我们自行实现重试机制,一般是在业务代码中加入一层循环,如果失败后,再尝试重试,但是这样实现并不优雅.在SpringBoot中,已经实现了相关的能力,通过@Retryable注解可以实现我们想要的结果. @Retryable 首先来看一下Spring官方文档的解释: @Retryable注解可以注解于方法

  • spring Retryable注解实现重试详解

    spring-boot:1.5.3.RELEASE,spring-retry-1.2.0.RELEASE 使用方法 引入pom // 版本号继承spring-boot依赖管理的pom <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency>

  • 如何优雅的抛出Spring Boot注解的异常详解

    前言 Spring Boot它简化了配置,内嵌式tomcat容器,用于快速开发基于Spring的应用,是一个微框架,本文主要介绍的是关于如何优雅的抛出Spring Boot注解的异常的相关内容,下面话不多说了,来一起看看详细的介绍吧 平时我们在写代码的时候肯定要进行很多参数验证,最开始的时候我们一般都是这样处理的 如下图 看起来好像也没什么,但是 如果参数多了呢?你就会看到这样的校验 OMG!!! 有没有感觉稍微有点视觉冲击,虽然这样写的已经很规整了,但是还是不够简单和优雅. 在SpringBo

  • Spring条件注解@Conditional示例详解

    前言 @Conditional是Spring4新提供的注解,它的作用是根据某个条件创建特定的Bean,通过实现Condition接口,并重写matches接口来构造判断条件.总的来说,就是根据特定条件来控制Bean的创建行为,这样我们可以利用这个特性进行一些自动的配置. 本文将分为三大部分,@Conditional源码的介绍.Condition的使用示例和@Conditional的扩展注解的介绍. 一.@Conditional的源码 @Target({ElementType.TYPE, Elem

  • Java之Spring注解开发案例详解

    在Spring4之后,要使用注解开发,必须要保证aop的包导入了 使用注解需要导入context约束,增加注解的支持! <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&

  • Dubbo在Spring和Spring Boot中的使用详解

    一.在Spring中使用Dubbo 1.Maven依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.3.6</version> <exclusions> <exclusion> <groupId>log4j</groupId> <artif

  • Spring Boot 集成MyBatis 教程详解

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者. 在集成MyBatis前,我们先配置一个druid数据源. Spring Boot 系列 1.Spring Boot 入门 2.Spring Boot 属性配置

  • spring、mybatis 配置方式详解(常用两种方式)

    在之前的文章中总结了三种方式,但是有两种是注解sql的,这种方式比较混乱所以大家不怎么使用,下面总结一下常用的两种总结方式: 一. 动态代理实现 不用写dao的实现类 这种方式比较简单,不用实现dao层,只需要定义接口就可以了,这里只是为了记录配置文件所以程序写的很简单: 1.整体结构图: 2.三个配置文件以及一个映射文件 (1).程序入口以及前端控制器配置 web.xml <?xml version="1.0" encoding="UTF-8"?> &

  • spring cloud-zuul的Filter使用详解

    在前面我们使用zuul搭建了网关http://www.jb51.net/article/133235.htm 关于网关的作用,这里就不再次赘述了,我们今天的重点是zuul的Filter.通过Filter,我们可以实现安全控制,比如,只有请求参数中有用户名和密码的客户端才能访问服务端的资源.那么如何来实现Filter了? 要想实现Filter,需要以下几个步骤: 1.继承ZuulFilter类,为了验证Filter的特性,我们这里创建3个Filter 根据用户名来过滤 package com.ch

  • spring定义和装配bean详解

    在阅读本文之前,大家可先参阅<简单理解Spring之IOC和AOP及代码示例>一文,了解下Spring中IOC和AOP的相关内容.下面进入正题.本篇文章介绍在Spring中如何定义和装载Java Bean. 业务场景 还是人开车的例子.首先,定义一个Car接口和两个实现了Benz和BMW,然后定义一个Person类,Person类依赖Car接口. public interface Car { void go(); } public class Benz implements Car { pub

  • spring mvc路径匹配原则详解

    在Spring MVC中经常要用到拦截器,在配置需要要拦截的路径时经常用到<mvc:mapping/>子标签,其有一个path属性,它就是用来指定需要拦截的路径的.例如: <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.i360r.platform.webapp.runtime.view.interceptor.GenericInterceptor"

随机推荐