Java 代码重试实现方式
- 一.方法内直接自动重试
- 二.静态代理方式
- 1.启动类
- 2.接口
- 3.实现
- 4.静态代理
- 5.单元测试类
- 三.JDK 动态代理
- 1.代理类
- 2.单元测试
- 四.CGLIB 动态代理
- 1.动态代理类
- 2.单元测试
- 五.手动 AOP
- 1.自定义注解
- 2.重试注解切面
- 3.测试类
- 4.单元测试方法
- 六.Spring Retry
- 1.测试类
- 2.单元测试类
- 3.单元测试方法
- 七.Guava Retry
- 1.测试类
- 2.单元测试
依赖包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>learn-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>20</maven.compiler.source>
<maven.compiler.target>20</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>3.1.2</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.11</version>
</dependency>
</dependencies>
</project>
一.方法内直接自动重试
public static void main(String[] args) {
autoRetry(3);
}
/**
* 自动重试(执行总次数包含于重试次数内)
* 优点:实现简单
* 缺点:复用性差,代码侵入
* @param retryTimes
*/
public static void autoRetry(int retryTimes){
int times = 0;
do {
try {
int i = 3/0;
} catch (Exception e) {
times++;
System.out.println("第" + times + "次失败");
}
} while (times < retryTimes);
}
二.静态代理方式
1.启动类
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Administrator
*/
@SpringBootApplication
public class LearnApp {
public static void main(String[] args) {
SpringApplication.run(LearnApp.class,args);
}
}
2.接口
package org.example.service;
/**
* @description
* @author zhuwd && moon
* @version 1.0
*/
public interface LearnService {
/**
* 自动重试
*/
void autoRetry();
}
3.实现
package org.example.service.impl;
import org.example.service.LearnService;
import org.springframework.stereotype.Service;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:05
*/
@Service
public class LearnServiceImpl implements LearnService {
@Override
public void autoRetry() {
throw new RuntimeException();
}
}
4.静态代理
package org.example.service.impl;
import org.example.service.LearnService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:06
*/
@Service
public class LearnServiceProxyImpl implements LearnService {
@Autowired
private LearnServiceImpl learnService;
private static final int RETRY_TIMES = 3;
@Override
public void autoRetry() {
int times = 0;
do {
try {
learnService.autoRetry();
} catch (Exception e) {
times++;
System.out.println("第" + times + "次失败");
}
} while (times < RETRY_TIMES);
}
}
5.单元测试类
import org.example.LearnApp;
import org.example.service.impl.LearnServiceProxyImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LearnApp.class)
public class LearnTest {
@Autowired
LearnServiceProxyImpl learnServiceProxy;
@Test
public void test(){
learnServiceProxy.autoRetry();
}
}
三.JDK 动态代理
1.代理类
package org.example.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author zhuwd && moon
* @Description 实现方式较优雅,但是被代理的类必须实现某个接口才能操作
* @create 2023-08-08 21:14
*/
public class RetryInvocationHandler implements InvocationHandler {
private static final int RETRY_TIMES = 3;
/**
* 目标对象
*/
private final Object target;
/**
* 有参构造
* @param target
*/
public RetryInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int times = 0;
do {
try {
return method.invoke(target,args);
} catch (Exception e) {
times++;
System.out.println("第" + times + "次失败");
}
} while (times < RETRY_TIMES);
return null;
}
}
2.单元测试
import org.example.LearnApp;
import org.example.proxy.RetryInvocationHandler;
import org.example.service.LearnService;
import org.example.service.impl.LearnServiceImpl;
import org.example.service.impl.LearnServiceProxyImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LearnApp.class)
public class LearnTest {
@Autowired
LearnServiceImpl learnService;
@Test
public void testJdk(){
InvocationHandler handler = new RetryInvocationHandler(learnService);
LearnService service = (LearnService) Proxy.newProxyInstance(handler.getClass().getClassLoader(), learnService.getClass().getInterfaces(),handler);
service.autoRetry();
}
}
四.CGLIB 动态代理
注意依赖选择 cglib 包下的,不要用 spring 下的
1.动态代理类
package org.example.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 21:25
*/
public class CGLibRetryProxyHandler implements MethodInterceptor {
private static final int RETRY_TIMES = 3;
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
int times = 0;
do {
try {
return method.invoke(target,objects);
} catch (Exception e) {
times++;
System.out.println("第" + times + "次失败");
}
} while (times < RETRY_TIMES);
return null;
}
/**
* 生产代理类
* @param target
* @return
*/
public Object getProxy(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
enhancer.setClassLoader(this.target.getClass().getClassLoader());
Object proxy = enhancer.create();
return proxy;
}
}
2.单元测试
import org.example.LearnApp;
import org.example.proxy.CGLibRetryProxyHandler;
import org.example.service.LearnService;
import org.example.service.impl.LearnServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LearnApp.class)
public class LearnTest {
@Autowired
LearnServiceImpl learnService;
@Test
public void testCglib(){
CGLibRetryProxyHandler handler = new CGLibRetryProxyHandler();
LearnService service = (LearnService) handler.getProxy(learnService);
service.autoRetry();
}
}
如果是JDK17及以上版本,用IDEA调试需要增加VM配置:--add-opens java.base/java.lang=ALL-UNNAMED
五.手动 AOP
利用自定义注解实现重试AOP逻辑
1.自定义注解
package org.example.anno;
import java.lang.annotation.*;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 22:50
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryAnno {
/**
* 重试次数
* @return
*/
int retryTimes() default 3;
/**
* 重试间隔(s)
* @return
*/
int retryInterval() default 3;
}
2.重试注解切面
package org.example.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.anno.RetryAnno;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 22:52
*/
@Aspect
@Component
public class RetryAspect {
@Pointcut("@annotation(org.example.anno.RetryAnno)")
private void retryCall(){};
@Around("retryCall()")
public Object retry(ProceedingJoinPoint joinPoint) throws Throwable{
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
RetryAnno retryAnno = signature.getMethod().getAnnotation(RetryAnno.class);
int retryTimes = retryAnno.retryTimes();
int retryInterval = retryAnno.retryInterval();
int times = 0;
do {
try {
return joinPoint.proceed();
} catch (Exception e) {
times++;
System.out.println(System.currentTimeMillis() + " 第" + times + "次失败");
TimeUnit.SECONDS.sleep(retryInterval);
}
} while (times < retryTimes);
return null;
}
}
3.测试类
package org.example.config;
import org.example.anno.RetryAnno;
import org.springframework.stereotype.Component;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 23:02
*/
@Component
public class LearnRetry {
@RetryAnno(retryTimes = 3,retryInterval = 3)
public void retry(){
throw new RuntimeException();
}
}
4.单元测试方法
import org.example.LearnApp;
import org.example.config.LearnRetry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LearnApp.class)
public class LearnTest {
@Autowired
LearnRetry learnRetry;
@Test
public void testAopRetry(){
learnRetry.retry();
}
}
六.Spring Retry
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.2</version>
</dependency>
1.测试类
package org.example.config;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 23:10
*/
@Component
@EnableRetry
public class SpringRetry {
/**
* maxAttempts 最大重试次数
* backoff.delay 重试延迟
* backoff.multiplier 延迟倍数,即第一次间隔 2S 第二次将间隔 4秒
*/
@Retryable(maxAttempts = 3,backoff = @Backoff(delay = 2000,multiplier = 2))
public void retry(){
System.out.println(System.currentTimeMillis() + " 重试 ...");
throw new RuntimeException();
}
/**
* @Recover 注解必须和 @Retryable 在同一个类;且返回值一致不能抛出额外异常
*
* @param e
*/
@Recover
public void recover(RuntimeException e){
System.out.println("已达最大重试次数");
}
}
2.单元测试类
import org.example.LearnApp;
import org.example.config.SpringRetry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LearnApp.class)
public class LearnTest {
@Autowired
SpringRetry springRetry;
@Test
public void testSpringRetry(){
springRetry.retry();
}
}
3.单元测试方法
import org.example.LearnApp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.HashMap;
import java.util.Map;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LearnApp.class)
public class LearnTest {
public void retry(){
System.out.println(System.currentTimeMillis() + " 重试 ...");
throw new RuntimeException();
}
@Test
public void testSpringRetryMethod(){
RetryTemplate template = new RetryTemplate();
/**
* 重试策略
*/
Map<Class<? extends Throwable>,Boolean> map = new HashMap<>();
map.put(RuntimeException.class,true);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3,map);
/**
* 重试回退策略
*/
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(2000);
template.setRetryPolicy(retryPolicy);
template.setBackOffPolicy(backOffPolicy);
Boolean execute = template.execute(retryContext -> {
retry();
return true;
},retryContext -> {
System.out.println("已达最大重试次数");
return false;
});
System.out.println("调用结果 " + execute);
}
}
七.Guava Retry
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
1.测试类
package org.example.config;
import org.springframework.stereotype.Component;
/**
* @author zhuwd && moon
* @Description 10S 后返回正确值
* @create 2023-08-08 23:49
*/
@Component
public class GuavaRetry {
private static long init_time = System.currentTimeMillis() + 1000 * 10;
public int retry(){
if (System.currentTimeMillis() > init_time){
return 0;
}
return -1;
}
}
2.单元测试
import com.github.rholder.retry.*;
import org.example.LearnApp;
import org.example.config.GuavaRetry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @author zhuwd && moon
* @Description
* @create 2023-08-08 1:11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LearnApp.class)
public class LearnTest {
@Autowired
GuavaRetry guavaRetry;
@Test
public void testGuavaRetry(){
Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
//根据异常重试
.retryIfException()
//根据结果重试
.retryIfResult(result-> !Objects.equals(result,0))
//重试策略
.withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
//停止策略
.withStopStrategy(StopStrategies.stopAfterAttempt(300))
//监听重试进度
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
System.out.println(System.currentTimeMillis() + " 第" + attempt.getAttemptNumber() + "次失败");
}
})
.build();
try {
int result = retryer.call(()->guavaRetry.retry());
System.out.println("调用结果:" + result);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (RetryException e) {
throw new RuntimeException(e);
}
}
}