优雅!Spring的重试小工具

一、介绍

在日常项目的开发中,避免不了调用第三方服务的情况。

如果是第三方有提供SDK包那还好说,就怕没有,第三方接口还不稳定的情况最恼火了。

这个时候,我们一般都会加上重试机制,手动捕获异常发起重试,不优雅

试试这个spring中的工具spring-retry如何

image-20230306171419108

官网

github地址

二、使用

导入maven依赖,使用的是SpringBoot框架,版本号已经有管理了,直接引入即可。

记得把AOP也引用一下

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

SpringBoot的启动类上加上@EnableRetry注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.test;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableRetry
@EnableScheduling
@MapperScan("com.banmoon.test.mapper")
@SpringBootApplication
public class TestApplication {

public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}

}

编写RetryController.java,里面包含了模拟的server方法,一会我们通过client方法去调用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.banmoon.test.controller;

import com.banmoon.test.obj.dto.ResultData;
import com.banmoon.test.service.RetryService;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@Slf4j
@Validated
@Api(tags = "重试测试")
@RestController
@RequestMapping("retry")
public class RetryController {

@Autowired
private RetryService retryService;

@GetMapping("server")
public ResultData<String> server(@RequestParam Integer delay) throws TimeoutException {
try {
boolean timeout = delay > 5000;
if (timeout) {
delay = 5000;
}
TimeUnit.MILLISECONDS.sleep(delay);
if (timeout) {
throw new TimeoutException();
}
} catch (InterruptedException e) {
log.error("睡眠异常");
}
return ResultData.success(delay+"");
}

@GetMapping("client")
public ResultData client(@RequestParam Integer delay) {
String result = retryService.callServer(delay);
return ResultData.success(result);
}

}

client方法用到了RetryService.java

1
2
3
4
5
6
package com.banmoon.test.service;

public interface RetryService {

String callServer(Integer delay);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.test.service.impl;

import cn.hutool.http.HttpUtil;
import cn.hutool.socket.SocketRuntimeException;
import com.alibaba.fastjson2.JSON;
import com.banmoon.test.obj.dto.ResultData;
import com.banmoon.test.service.RetryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class RetryServiceImpl implements RetryService {

@Override
@Retryable(value = {SocketRuntimeException.class, SocketTimeoutException.class}, maxAttempts = 3)
public String callServer(Integer delay) {
log.info("客户端请求服务端,delay:{}", delay);
Map<String, Object> params = new HashMap<>();
params.put("delay", delay);
String result = HttpUtil.get("http://localhost:8089/retry/server", params, 5000);
ResultData<String> data = JSON.parseObject(result, ResultData.class);
return data.getData();
}
}

主要就是这行注解

@Retryable(value = {SocketRuntimeException.class, SocketTimeoutException.class}, maxAttempts = 3)

发起重试的异常,重试的次数

具体可以看文档,或者源码

三、测试

启动服务,发送请求

image-20230306174646082

响应是这样的,我们继续看控制台,成功发起重试

image-20230306170024983

四、最后

在文档的示例中,我们也可以这样发起重试,如下

1
2
3
4
5
6
7
8
9
RetryTemplate template = RetryTemplate.builder()
.maxAttempts(3)
.fixedBackoff(1000)
.retryOn(RemoteAccessException.class)
.build();

template.execute(ctx -> {
// ... do something
});

我是半月,你我一同共勉!