有这样一个需求,当调用某个方法抛出异常,比如通过 HttpClient 调用远程接口时由于网络原因报 TimeOut 异常;或者所请求的接口返回类似于“处理中”这样的信息,需要重复去查结果时,我们希望当前方法能够在这种特定的情况下,重复执行,如果达到了我们的期望,则不重复执行。而且,我们希望能够控制重试次数,不希望无限期执行下去。

Java 中有各种定时任务的实现,如 Spring 的 Schedule,Quartz 等,稍微想一下,显然不符合我们的需求。递归倒是可以,但是有些问题,先看下递归的实现:

    private int retryTimes = 3;

    @Test
public void upperMethod() {
method("123", "456");
}
public void method(String param1, String param2) {
System.out.println(param1 + param2); // 其他一些操作,但是没有得到预期的返回结果,或者抛出异常
boolean isException = true;
if(isException && retryTimes > 0){
retryTimes --;
method(param1, param2);
}
}

method 方法是需要重复执行的,重复执行 3 次,加上第一次执行,一共 4 次。如果异常了,则在 catch 里面递归调用 method。如果返回“处理中”等情况,则进行判断,是否需要递归调用。

这里的问题是定义了 retryTimes 这样一个全局变量,不优雅,如果需要重复执行的方法较多,而且重复次数不一样,则需定义多个全局变量。递归可以优化一下:

    @Test
public void upperMethod() {
method(3, "123", "456");
} public void method(int retryTimes,String param1, String param2) {
System.out.println(param1 + param2); // 其他一些操作,但是没有得到预期的返回结果,或者抛出异常
boolean isException = true;
if(isException && retryTimes > 0){
method(--retryTimes, param1, param2);
}
}

这里去掉了全局变量,但是 method 方法多了一个和自身逻辑无关的 retryTimes 变量,还不优雅。如果参数较多,还会显得混乱。

下面做了一个还算优雅的方法:

    @Test
public void mainMethod() {
subMethod("123", "456");
} public void subMethod(String param1, String param2) {
System.out.println(param1 + param2);
RetryUtil.setRetryTimes(3).retry(param1, param2);
}

增加了一个 RetryUtil 的工具类,设置重试次数,然后传入当前方法的参数,进行重复执行。这里的重点就是 RetryUtil 的实现:

public class RetryUtil {
private static ThreadLocal<Integer> retryTimesInThread = new ThreadLocal<>(); /**
* 设置当前方法重试次数
*
* @param retryTimes
* @return
*/
public static RetryUtil setRetryTimes(Integer retryTimes) {
if (retryTimesInThread.get() == null)
retryTimesInThread.set(retryTimes);
return new RetryUtil();
} /**
* 重试当前方法
* <p>按顺序传入调用者方法的所有参数</p>
*
* @param args
* @return
*/
public Object retry(Object... args) {
try {
Integer retryTimes = retryTimesInThread.get();
if (retryTimes <= 0) {
retryTimesInThread.remove();
return null;
}
retryTimesInThread.set(--retryTimes);
String upperClassName = Thread.currentThread().getStackTrace()[2].getClassName();
String upperMethodName = Thread.currentThread().getStackTrace()[2].getMethodName(); Class clazz = Class.forName(upperClassName);
Object targetObject = clazz.newInstance();
Method targetMethod = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(upperMethodName)) {
targetMethod = method;
break;
}
}
if (targetMethod == null)
return null;
targetMethod.setAccessible(true);
return targetMethod.invoke(targetObject, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

为了防止多线程情况下出现并发问题,这里定义了一个 ThreadLocal 变量来存储当前线程的重试次数。然后通过 setRetryTimes ,一个静态方法来设置这个重试次数,并返回一个 RetryUtil 对象。

调用者通过返回的 RetryUtil 对象调用 retry 方法实现重试。retry 方法接收一个可变参数,因为调用者实际的参数不确定,这里要求按顺序传入调用者方法的所有参数。

接下来判断 ThreadLocal 变量是否小于等于 0 ,如果是,则说明重复次数已达到,返回 null;如果不是,则让 ThreadLocal 变量减一。接下来:

String upperClassName = Thread.currentThread().getStackTrace()[2].getClassName();
String upperMethodName = Thread.currentThread().getStackTrace()[2].getMethodName();

来获取当前方法(retry)的上层方法名和上层类名。Thread.currentThread().getStackTrace() 得到线程的方法栈数组,数组的第二个元素 Thread.currentThread().getStackTrace() [1]  为当前方法栈,第三个元素 Thread.currentThread().getStackTrace() [2] 为上层方法栈,通过上层方法的栈帧得到上层方法的方法名和类名。

下面就是通过反射获取该类的所有方法,循环判断方法名是否等于所要重复执行的方法,如果是的话,执行该方法,参数就是传入可变参数。

可能大家会说反射会耗时,但我认为对于上述这种需求的情况,重试次数也不会太多,因此性能可以接受。

java-retry实现的更多相关文章

  1. Java Retry implement

    There are many cases in which you may wish to retry an operation a certain number of times. Examples ...

  2. java retry:详解

    发现 今天在探秘线程池原理知识点,在阅读JDK源码时遇到程序代码中出现如下代码,因为之前没有遇到过,于是特地记录下来并谷歌了一番,后面我自己做了一些简要的验证和分析. 验证 网上溜达一番发现,这ret ...

  3. 更好的 java 重试框架 sisyphus 入门简介

    What is Sisyphus sisyphus 综合了 spring-retry 和 gauva-retrying 的优势,使用起来也非常灵活. 为什么选择这个名字 我觉得重试做的事情和西西弗斯很 ...

  4. Spark案例分析

    一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...

  5. java function retry wrapper

    import java.util.concurrent.Callable; /** * Created by huahui.yang on 1/29/16. */ public class Retry ...

  6. 【java基础】Java的retry标记的使用

    说明:其实retry就是一个标记,标记程序跳出循环的时候从哪里开始执行,功能类似于goto.retry一般都是跟随者for循环出现,第一个retry的下面一行就是for循环,而且第二个retry的前面 ...

  7. retry.RetryInvocationHandler (RetryInvocationHandler.java:invoke(140)) - Exception while invoking getFileInfo of class ClientNamenodeProtocolTranslatorPB over bdata236/192.168.1.236:9000 after 3 fail

    报错信息如下 -- ::, INFO [main]: retry.RetryInvocationHandler (RetryInvocationHandler.java:invoke()) - Exc ...

  8. Java之Retry重试机制详解

    应用中需要实现一个功能: 需要将数据上传到远程存储服务,同时在返回处理成功情况下做其他操作.这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务上传数据后对返回的结果进行处理:第二步拿到第一步结 ...

  9. 非root用户执行java进程报错:fork: retry:资源暂时不可用

    vim /etc/security/limits.conf # End of file *           soft   nproc        65535 *           hard   ...

  10. Java集合---ConcurrentHashMap原理分析

    集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主 ...

随机推荐

  1. 20180726 - Windows 10 Pro 下远程桌面连接提示“出现身份验证错误”

    问题:Windows 10 Pro 下远程桌面连接提示“出现身份验证错误” [Window Title]远程桌面连接 [Content]出现身份验证错误.要求的函数不受支持 远程计算机: 192.16 ...

  2. Centos7.3离线(rpm方式)安装mysql服务

    1.mysql官网下载安装包,官网地址:www.mysql.com [root@seiang software]# ll total 580020 -rw-r--r--. 1 root root 59 ...

  3. HDP 2.6 requires libtirpc-devel

    HDP 2.6 requires libtirpc-devel 个问题,截止 Mustafa Kemal MAYUK 2017年06月30日 06:30 hadoopPowerSystems Hell ...

  4. 【工具篇】抓包中的王牌工具—Fiddler (2-工具介绍)

    Fiddler 抓包工具界面简介 1) 字段说明 Fiddler想要抓到数据包,要确保Capture Traffic是开启,在File –> Capture Traffic. 开启后再左下角会有 ...

  5. Python后台开发Django(数据库)

    如果使用pymysql,则可以在view中直接import pymysql进行操作,与原操作无区别 Django数据库框架支持 sqlite3, MySQL, PostgreSQL等数据库,只需要在s ...

  6. Vue2.0源码阅读笔记(一):选项合并

      Vue本质是上来说是一个函数,在其通过new关键字构造调用时,会完成一系列初始化过程.通过Vue框架进行开发,基本上是通过向Vue函数中传入不同的参数选项来完成的.参数选项往往需要加以合并,主要有 ...

  7. Tesseract 在 windows 下的安装及简单应用

    Tesseract 是一个开源的 OCR 引擎,可以识别多种格式的图像文件并将其转换成文本,最初由 HP 公司开发,后来由 Google 维护.下载地址:https://digi.bib.uni-ma ...

  8. 一套代码小程序&Web&Native运行的探索03——处理模板及属性

    接上文:一套代码小程序&Web&Native运行的探索02 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/m ...

  9. 2.5配置的框架浅析「深入浅出ASP.NET Core系列」

    希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,谢谢关注. 配置的使用流程 //第一步.初始化Builder var builder = new ConfigurationB ...

  10. [转]Node.js 应用:Koa2 使用 JWT 进行鉴权

    本文转自:https://www.cnblogs.com/linxin/p/9491342.html 前言 在前后端分离的开发中,通过 Restful API 进行数据交互时,如果没有对 API 进行 ...