在有些调用http请求功能中,会因为网络的抖动,使得网络不稳定。这时需要一个功能来实现重试。
下面介绍使用JDK的代理功能和Cglib来实现简单的代理重试。
JDK的代理需要被代理的类实现接口,下面的newProxyInstance代码中需要interfaces参数:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
Cglib是代码生成库,无需被代理类实现接口。
代理原理:
- 动态代理对象的创建原理是假设创建的代理对象名为 $Proxy0:
- 根据传入的interfaces动态生成一个类,实现interfaces中的接口
- 通过传入的classloder将刚生成的类加载到jvm中。即将$Proxy0类load
- 调用 P r o x y 0 的 Proxy0的 Proxy0的Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象,并且用interfaces参数遍- 历其所有接口的方法,并生成实现方法,这些实现方法的实现本质上是通过反射调用被代理对象的方法。
- 将$Proxy0的实例返回给客户端。
- 当调用代理类的相应方法时,相当于调用 InvocationHandler.invoke(Object, Method, Object []) 方法。
1、JDK代理实现重试
RetryProxyHandler.java
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
@Slf4j
public class RetryProxyHandler implements InvocationHandler {
private final Object subject;
public RetryProxyHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int times = 1;
while (times <= 5) {
try {
int i = 3 / 0;
return method.invoke(subject, args);
} catch (Exception e) {
log.info("重试【" + times + "】次");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
return null;
}
public static Object getProxy(Object realSubject) {
InvocationHandler handler = new RetryProxyHandler(realSubject);
return Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
}
}
RetryDoI.java
public interface RetryDoI {
public void doSomething();
}
RetryDoImpl.java
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RetryDoImpl implements RetryDoI {
@Override
public void doSomething() {
log.info("do something ........");
}
}
测试:
public class Demo {
public static void main(String[] args) {
RetryDoImpl retryDo = new RetryDoImpl();
RetryDoI retryDoIProxy =(RetryDoI) RetryProxyHandler.getProxy(retryDo);
retryDoIProxy.doSomething();
}
}
输出结果:
2、Cglib实现重试
使用Hutool工具包的Cglib代理工具实现重试。
maven的pom.xml引入:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.7</version>
</dependency>
RetryDoAspect .java
import cn.hutool.aop.aspects.SimpleAspect;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.lang.Console;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
@Slf4j
public class RetryDoAspect extends SimpleAspect {
@Override
public boolean before(Object target, Method method, Object[] args) {
int times = 1;
while (times <= 5) {
try {
int i = 3 / 0;
method.invoke(target, args);
} catch (Exception e) {
log.warn("重试【" + times + "】次");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
return true;
}
@Override
public boolean after(Object target, Method method, Object[] args, Object returnVal) {
log.info("Method [{}.{}] ", target.getClass().getName(), method.getName());
return true;
}
}
RetryDoV2 .java
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RetryDoV2 {
public void doSomething() {
log.info("do something ........");
}
}
hutool根据中生成代理的代码:
public class CglibProxyFactory extends ProxyFactory{
private static final long serialVersionUID = 1L;
@Override
@SuppressWarnings("unchecked")
public <T> T proxy(T target, Aspect aspect) {
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new CglibInterceptor(target, aspect));
return (T) enhancer.create();
}
}
测试代码:
import cn.hutool.aop.ProxyUtil;
public class Demo {
public static void main(String[] args) {
RetryDoV2 retryDoV2 = ProxyUtil.proxy(new RetryDoV2(), RetryDoAspect.class);
retryDoV2.doSomething();
}
}
测试结果:
另外对于spring项目,有现成的组件库区实现重试功能,spring-retry,重试就是一个标签的问题。
重试组件库有:Spring Retry、Google guava-retrying,有兴趣的可以去了解一下。