Spring框架实战技巧 - 使用@Retryable注解实现业务自动重试

2 分钟阅读

@Retryable 是 Spring Retry 模块提供的一个注解,用于声明一个方法需要在失败时进行重试。如果一个方法使用了 @Retryable 注解,那么当执行该方法时,如果发生了异常,则 Spring 会自动进行重试,直到达到最大重试次数或者重试成功为止。

本文示例代码基于Spring-Retry模块 1.3.4.RELEASE 版本.

Spring-Retry 2.x 版本,在后续有空再填坑。

下面通过一个简单的示例,开始本教程的学习。 首先,再build.gradle文件中添加依赖,导入Spring-Retry模块

1
implementation 'org.springframework.retry:spring-retry:1.3.4.RELEASE'

在方法上使用@Retryable注解,示例代码如下:

1
2
3
4
@Retryable(RuntimeException.class)
public void doSomething() {
        // 业务代码
}

以上是使用注解方式定义的 Spring Retry 的重试机制。在上述示例中,我们声明了一个 doSomething() 方法,并使用了 @Retryable(RuntimeException.class) 注解来告诉 Spring 当遇到 RuntimeException 异常时需要进行重试。默认情况下,Spring Retry 最多会重试 3 次。

interceptor属性

interceptor 属性指定了要使用的拦截器类名。

1
2
3
4
@Retryable(value = { IOException.class }, interceptor = "myRetryInterceptor")
public void doSomething() {
        // 业务代码
}

在上述示例中,我们使用了 myRetryInterceptor 拦截器类来处理重试逻辑。

value属性

value 属性指定了可以触发重试的异常类型。

1
2
3
4
@Retryable(value = { IOException.class, SQLException.class })
public void doSomething() {
        // 业务代码
}

在上述示例中,我们指定了只有当发生 IOExceptionSQLException 异常时才会触发重试。

include和exclude属性

includeexclude 属性可以用来控制哪些异常类型可以或不可以被重试。

1
2
3
4
@Retryable(include = { NetworkException.class }, exclude = { TimeoutException.class })
public void doSomething() {
        // 业务代码
}

在上述示例中,我们指定了只有当发生 NetworkException 异常时才会触发重试,而 TimeoutException 异常不会被重试。

label属性

label 属性可以用来给重试操作设置标签。

1
2
3
4
@Retryable(value = RuntimeException.class, label = "my-retry-label")
public void doSomething() {
        // 业务代码
}

在上述示例中,我们给重试操作设置了一个名为 my-retry-label 的标签。

stateful属性

stateful 属性指定了是否启用状态模式。默认情况下,它是关闭的。

1
2
3
4
@Retryable(value = RuntimeException.class, stateful = true)
public void doSomething() {
        // 业务代码
}

在上述示例中,我们启用了状态模式。

maxAttempts和maxAttemptsExpression属性

maxAttempts 属性指定了最大重试次数。

1
2
3
4
@Retryable(value = RuntimeException.class, maxAttempts = 5)
public void doing() {
        // 业务代码
}

在上述示例中,我们指定了最大重试次数为 5 次。

maxAttemptsExpression 属性可以用来动态地设置最大重试次数。它需要一个 SpEL 表达式作为参数。

1
2
3
4
@Retryable(value = RuntimeException.class, maxAttemptsExpression = "#{retryConfiguration.maxAttempts}")
public void doSomething() {
        // 业务代码
}

在上述示例中,我们使用 SpEL 表达式来动态地设置最大重试次数。

backoff属性

backoff 注解用于指定重试操作的退避策略。退避策略是用于控制重试操作之间的延迟时间的一组规则。

属性名 属性含义 属性作用和途
value int[]类型,表示延迟时间 定义重试时每次重试的延迟时间,单位为毫秒。如果仅指定一个整数值,则表示所有的重试延迟时间都为该值。如果指定多个整数值,则每次重试的延迟时间将从这些值中随机选择一个。
delay long类型,表示初始延迟时间 定义重试工作开始之前的延迟时间,单位为毫秒。例如,如果设置为500L,则将在第一次重试之前等待500毫秒。
maxDelay long类型,表示最大延迟时间 定义重试工作期间的最大延迟时间,单位为毫秒。如果在重试过程中延迟超过此时间,则不再重试,并且抛出重试失败异常。
multiplier double类型,表示延迟时间倍增因子 定义每次重试延迟时间的倍增因子。例如,如果设置为1.5,则每次重试的延迟时间都会增加50%。
delayExpression String类型,表示初始延迟时间表达式 使用Spring表达式语言定义重试工作开始之前的延迟时间。例如,如果设置为”#{T(java.lang.Math).random() * 1000}”,则将在第一次重试之前等待随机500到1000毫秒的时间。
maxDelayExpression String类型,表示最大延迟时间表达式 使用Spring表达式语言定义重试工作期间的最大延迟时间。例如,如果设置为”#{10000}”,则将最大延迟时间设置为10000毫秒。
multiplierExpression String类型,表示延迟时间倍增因子表达式 使用Spring表达式语言定义每次重试延迟时间的倍增因子。例如,如果设置为”#{1.0/2}”,则每次重试的延迟时间都会减少一半。
random boolean类型,表示是否随机化 定义是否启用随机化延迟时间。如果设置为true,则每次重试的延迟时间将从[0,value]+jitter之间随机选择。其中value为value属性中指定的值,jitter是一个随机值,其范围在0到value之间。默认值为false。

通过一个简单的例子加深理解。

1
2
3
4
@Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
public void doSomething() {
        // 业务代码
}

在上述示例中,我们指定了在每次重试之间等待 1 秒,并且每次重试后等待时间加倍。

exceptionExpression属性

exceptionExpression 属性用于控制哪些异常类型应该被重试。它接收一个 SpEL 表达式作为参数。

1
2
3
4
@Retryable(exceptionExpression = "#{message.contains('Not Found')}")
public void doSomething() {
        // 业务代码
}

在上述示例中,我们使用 SpEL 表达式来判断只有当错误消息包含 “Not Found” 时才会触发重试。

listeners方法

listeners 方法可以用来添加重试操作监听器。

1
2
3
4
@Retryable(value = RuntimeException.class, listeners = {"myRetryListener"})
public void doSomething() {
        // 业务代码
}

在上述示例中,我们给重试操作添加了一个名为 myRetryListener 的监听器。

这是重试监听器的一种实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component("myRetryListener")
public class MyRetryListener implements RetryListener {
    
    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
            return true;
    }

    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
            Throwable throwable) {
    
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
            Throwable throwable) {
            System.out.println("Failed to execute: " + throwable.getMessage());
    }

}

RetryTemplate

除了注解方式,Spring Retry 还提供了编程式的重试方式。下面是一个使用编程式方式实现重试的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RetryTemplate retryTemplate = new RetryTemplate();

FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000L);

SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(3);

retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
retryTemplate.setRetryPolicy(simpleRetryPolicy);

retryTemplate.execute(context -> {
        // 业务代码
    return null;
});

在上述示例中,我们创建了一个 RetryTemplate 实例,并设置了重试策略和重试间隔时间。然后我们调用 execute() 方法并将业务逻辑代码放入其中。

总结

Spring Retry 提供了多种方式来实现重试机制,包括注解方式、编程式方式,以及自定义的拦截器和监听器等。这些功能在处理失败操作时非常有用,并且可以使应用程序更加稳定和可靠。

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 TinyZ Zzh (包含链接: https://tinyzzh.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。 如有任何疑问,请 与我联系 (tinyzzh815@gmail.com)

TinyZ Zzh

TinyZ Zzh

专注于高并发服务器、网络游戏相关(Java、PHP、Unity3D、Unreal Engine等)技术,热爱游戏事业, 正在努力实现自我价值当中。

评论

  点击开始评论...