【最佳实践】如何优雅的进行重试 (2)

来一发单元测:

@Test public void helloDynamicProxy() { IHelloService realService = new HelloService(); IHelloService proxyService = (IHelloService)RetryInvocationHandler.getProxy(realService); String hello = proxyService.hello(); log.info("hello:{}", hello); }

输出如下:

hello times:1 发生异常,time:11:22:20.727586700 times:1,time:11:22:20.728083 hello times:2 发生异常,time:11:22:21.728858700 times:2,time:11:22:21.729343700 hello times:3 发生异常,time:11:22:22.729706600 times:3,time:11:22:22.729706600 hello times:4 hello:hello

在重试了4次之后输出了Hello,符合预期。

动态代理可以将重试逻辑都放到一块,显然比直接使用代理类要方便很多,也更加优雅。

不过不要高兴的太早,这里因为被代理的HelloService是一个简单的类,没有依赖其它类,所以直接创建是没有问题的,但如果被代理的类依赖了其它被Spring容器管理的类,则这种方式会抛出异常,因为没有把被依赖的实例注入到创建的代理实例中。

这种情况下,就比较复杂了,需要从Spring容器中获取已经装配好的,需要被代理的实例,然后为其创建代理类实例,并交给Spring容器来管理,这样就不用每次都重新创建新的代理类实例了。

话不多说,撸起袖子就是干。

timg.jpg

新建一个工具类,用来获取代理实例:

@Component public class RetryProxyHandler { @Autowired private ConfigurableApplicationContext context; public Object getProxy(Class clazz) { // 1. 从Bean中获取对象 DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)context.getAutowireCapableBeanFactory(); Map<String, Object> beans = beanFactory.getBeansOfType(clazz); Set<Map.Entry<String, Object>> entries = beans.entrySet(); if (entries.size() <= 0){ throw new ProxyBeanNotFoundException(); } // 如果有多个候选bean, 判断其中是否有代理bean Object bean = null; if (entries.size() > 1){ for (Map.Entry<String, Object> entry : entries) { if (entry.getKey().contains(PROXY_BEAN_SUFFIX)){ bean = entry.getValue(); } }; if (bean != null){ return bean; } throw new ProxyBeanNotSingleException(); } Object source = beans.entrySet().iterator().next().getValue(); Object source = beans.entrySet().iterator().next().getValue(); // 2. 判断该对象的代理对象是否存在 String proxyBeanName = clazz.getSimpleName() + PROXY_BEAN_SUFFIX; Boolean exist = beanFactory.containsBean(proxyBeanName); if (exist) { bean = beanFactory.getBean(proxyBeanName); return bean; } // 3. 不存在则生成代理对象 bean = RetryInvocationHandler.getProxy(source); // 4. 将bean注入spring容器 beanFactory.registerSingleton(proxyBeanName, bean); return bean; } }

使用的是JDK动态代理:

@Slf4j public class RetryInvocationHandler implements InvocationHandler { private final Object subject; public RetryInvocationHandler(Object subject) { this.subject = subject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { int times = 0; while (times < RetryConstant.MAX_TIMES) { try { return method.invoke(subject, args); } catch (Exception e) { times++; log.info("retry times:{},time:{}", times, LocalTime.now()); if (times >= RetryConstant.MAX_TIMES) { throw new RuntimeException(e); } } // 延时一秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } /** * 获取动态代理 * * @param realSubject 代理对象 */ public static Object getProxy(Object realSubject) { InvocationHandler handler = new RetryInvocationHandler(realSubject); return Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler); } }

至此,主要代码就完成了,修改一下HelloService类,增加一个依赖:

@Slf4j @Component public class HelloService implements IHelloService{ private static AtomicLong helloTimes = new AtomicLong(); @Autowired private NameService nameService; public String hello(){ long times = helloTimes.incrementAndGet(); log.info("hello times:{}", times); if (times % 4 != 0){ log.warn("发生异常,time:{}", LocalTime.now() ); throw new HelloRetryException("发生Hello异常"); } return "hello " + nameService.getName(); } }

NameService其实很简单,创建的目的仅在于测试依赖注入的Bean能否正常运行。

@Service public class NameService { public String getName(){ return "Frank"; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwfzxw.html