【Java】代理实现重试功能

在有些调用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的 Proxy0Proxy0(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();
    }
}

输出结果:
【Java】代理实现重试功能_第1张图片

2、Cglib实现重试

使用Hutool工具包的Cglib代理工具实现重试。
maven的pom.xml引入:

        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.8.16version>
        dependency>
        
		<dependency>
			<groupId>cglibgroupId>
			<artifactId>cglibartifactId>
			<version>3.2.7version>
		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();
    }
}

测试结果:
【Java】代理实现重试功能_第2张图片

另外对于spring项目,有现成的组件库区实现重试功能,spring-retry,重试就是一个标签的问题。

重试组件库有:Spring Retry、Google guava-retrying,有兴趣的可以去了解一下。

你可能感兴趣的:(java,java,开发语言)