From c317ef1e08ed9f88f9ab0f768dcd1e0e6971f342 Mon Sep 17 00:00:00 2001 From: kongweiguang Date: Mon, 19 Jun 2023 12:59:20 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hutool/core/thread/RetryableTask.java | 205 ++++++++++++++++++ .../dromara/hutool/core/util/RetryUtil.java | 148 +++++++++++++ .../hutool/core/util/RetryUtilTest.java | 91 ++++++++ 3 files changed, 444 insertions(+) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/thread/RetryableTask.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java create mode 100644 hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/thread/RetryableTask.java b/hutool-core/src/main/java/org/dromara/hutool/core/thread/RetryableTask.java new file mode 100644 index 000000000..d2d816561 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/thread/RetryableTask.java @@ -0,0 +1,205 @@ +package org.dromara.hutool.core.thread; + +import org.dromara.hutool.core.lang.Assert; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; +import java.util.function.Supplier; + +import static java.util.Objects.nonNull; + +/** + * 重试任务类 + * + * @author kongweiguang + * @since 6.0.0 + */ +public class RetryableTask { + /** + * 执行结果 + */ + private T result; + /** + * 执行法方法 + */ + private final Supplier sup; + /** + * 重试策略 + */ + private final BiPredicate predicate; + /** + * 重试次数,默认3次 + */ + private long maxAttempts = 3; + /** + * 重试间隔,默认1秒 + */ + private Duration delay = Duration.ofSeconds(1); + + /** + * 构造方法,内部使用,调用请使用请用ofXXX + * + * @param sup 执行的方法 + * @param predicate 策略 {@link BiPredicate} + */ + private RetryableTask(Supplier sup, BiPredicate predicate) { + Assert.notNull(sup, "task parameter cannot be null"); + Assert.notNull(predicate, "predicate parameter cannot be null"); + + this.predicate = predicate; + this.sup = sup; + } + + + /** + * 重试根据指定的异常,没有返回值 + * + * @param 返回值类型 + * @param run 执行的方法 {@link Runnable} + * @param ths 指定异常 {@link Throwable} + * @return 当前对象 {@link RetryableTask} + */ + @SafeVarargs + public static RetryableTask retryForExceptions(Runnable run, Class... ths) { + return retryForExceptions(() -> { + run.run(); + return null; + }, ths); + } + + /** + * 重试根据指定的策略,没有返回值 + * + * @param 返回值类型 + * @param run 执行的方法 {@link Runnable} + * @param predicate 策略 {@link BiPredicate} + * @return 当前对象 {@link RetryableTask} + */ + public static RetryableTask retryForPredicate(Runnable run, BiPredicate predicate) { + return retryForPredicate(() -> { + run.run(); + return null; + }, predicate); + } + + /** + * 重试根据指定的异常,有返回值 + * + * @param 返回值类型 + * @param sup 执行的方法 {@link Supplier} + * @param ths 指定异常 {@link Throwable} + * @return 当前对象 {@link RetryableTask} + */ + @SafeVarargs + public static RetryableTask retryForExceptions(Supplier sup, Class... ths) { + Assert.isTrue(ths.length != 0, "exs cannot be empty"); + + BiPredicate strategy = (t, e) -> { + if (nonNull(e)) { + return Arrays.stream(ths).anyMatch(ex -> ex.isAssignableFrom(e.getClass())); + } + return false; + }; + + return new RetryableTask<>(sup, strategy); + } + + + /** + * 重试根据指定的策略,没有返回值 + * + * @param 返回值类型 + * @param sup 执行的方法 {@link Supplier} + * @param predicate 策略 {@link BiPredicate} + * @return 当前对象 {@link RetryableTask} + */ + public static RetryableTask retryForPredicate(Supplier sup, BiPredicate predicate) { + return new RetryableTask<>(sup, predicate); + } + + + /** + * 最大重试次数 + * + * @param maxAttempts 次数 + * @return 当前对象 {@link RetryableTask} + */ + public RetryableTask maxAttempts(long maxAttempts) { + Assert.isTrue(this.maxAttempts > 0, "maxAttempts must be greater than 0"); + + this.maxAttempts = maxAttempts; + return this; + } + + /** + * 重试间隔时间 + * + * @param delay 间隔时间 + * @return 当前对象 {@link RetryableTask} + */ + public RetryableTask delay(Duration delay) { + Assert.notNull(this.delay, "delay parameter cannot be null"); + + this.delay = delay; + return this; + } + + /** + * 获取结果 + * + * @return 返回包装了结果的 {@link Optional}对象 + */ + public Optional get() { + return Optional.ofNullable(this.result); + } + + /** + * 异步执行重试方法 + * + * @return 返回一个异步对象 {@link CompletableFuture} + */ + public CompletableFuture> asyncExecute() { + return CompletableFuture.supplyAsync(this::doExecute, GlobalThreadPool.getExecutor()); + } + + /** + * 同步执行重试方法 + * + * @return 当前对象 {@link RetryableTask} + */ + public RetryableTask execute() { + return doExecute(); + } + + /** + * 开始重试 + * + * @return 当前对象 {@link RetryableTask} + **/ + private RetryableTask doExecute() { + Throwable th = null; + + while (--this.maxAttempts >= 0) { + + try { + this.result = this.sup.get(); + } catch (Throwable t) { + th = t; + } finally { + //判断重试 + if (this.predicate.test(this.result, th)) { + ThreadUtil.sleep(delay.toMillis()); + } else { + break; + } + } + } + + return this; + } + +} + diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java new file mode 100644 index 000000000..37e482e2a --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java @@ -0,0 +1,148 @@ +package org.dromara.hutool.core.util; + +import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.thread.GlobalThreadPool; +import org.dromara.hutool.core.thread.RetryableTask; +import org.dromara.hutool.core.thread.ThreadUtil; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; +import java.util.function.Supplier; + +/** + * 重试工具类 + * 自定义功能请使用{@link RetryableTask}类 + * + * @author kongweiguang + * @see RetryableTask + * @since 6.0.0 + */ +public class RetryUtil { + + /** + * 根据异常信息进行重试 + * 没有返回值,重试执行方法 + * + * @param run 执行方法 + * @param maxAttempts 最大的重试次数 + * @param delay 重试间隔 + * @param recover 达到最大重试次数后执行的备用方法,入参是重试过程中的异常 + * @param exs 指定的异常类型需要重试 + */ + @SafeVarargs + public static void ofException(Runnable run, long maxAttempts, Duration delay, Runnable recover, Class... exs) { + if (ArrayUtil.isEmpty(exs)) { + exs = ArrayUtil.append(exs, RuntimeException.class); + } + RetryableTask.retryForExceptions(run, exs) + .maxAttempts(maxAttempts) + .delay(delay) + .execute() + .get() + .orElseGet(() -> { + recover.run(); + return null; + }); + } + + /** + * 根据异常信息进行重试 + * 有返回值,重试执行方法 + * + * @param sup 执行方法 + * @param maxAttempts 最大的重试次数 + * @param delay 重试间隔 + * @param recover 达到最大重试次数后执行的备用方法,入参是重试过程中的异常 + * @param exs 指定的异常类型需要重试 + * @param 结果类型 + * @return 执行结果 + */ + @SafeVarargs + public static T ofException(Supplier sup, long maxAttempts, Duration delay, Supplier recover, Class... exs) { + if (ArrayUtil.isEmpty(exs)) { + exs = ArrayUtil.append(exs, RuntimeException.class); + } + return RetryableTask.retryForExceptions(sup, exs) + .maxAttempts(maxAttempts) + .delay(delay) + .execute() + .get() + .orElseGet(recover); + } + + /** + * 根据自定义结果进行重试 + * 没有返回值,重试执行方法 + * + * @param run 执行方法 + * @param maxAttempts 最大的重试次数 + * @param delay 重试间隔 + * @param recover 达到最大重试次数后执行的备用方法,入参是重试过程中的异常 + * @param predicate 自定义重试条件 + */ + public static void ofPredicate(Runnable run, long maxAttempts, Duration delay, Supplier recover, BiPredicate predicate) { + RetryableTask.retryForPredicate(run, predicate) + .delay(delay) + .maxAttempts(maxAttempts) + .execute() + .get() + .orElseGet(recover); + } + + + /** + * 根据异常信息进行重试 + * 有返回值,重试执行方法 + * + * @param sup 执行方法 + * @param maxAttempts 最大的重试次数 + * @param delay 重试间隔 + * @param recover 达到最大重试次数后执行的备用方法,入参是重试过程中的异常 + * @param predicate 自定义重试条件 + * @param 结果类型 + * @return 执行结果 + */ + public static T ofPredicate(Supplier sup, long maxAttempts, Duration delay, Supplier recover, BiPredicate predicate) { + return RetryableTask.retryForPredicate(sup, predicate) + .delay(delay) + .maxAttempts(maxAttempts) + .execute() + .get() + .orElseGet(recover); + } + + + /** + * 从不停止的执行方法 + * + * @param run 执行方法 + * @param delay 间隔时间 + * @param isEx true:出现异常继续执行;false:则出现异常跳出执行。 + */ + public static void ofNeverStop(Runnable run, Duration delay, boolean isEx) { + while (true) { + try { + run.run(); + } catch (Throwable e) { + if (!isEx) { + break; + } + } finally { + ThreadUtil.sleep(delay.toMillis()); + } + } + } + + /** + * 从不停止的执行方法,异步执行 + * + * @param run 执行方法 + * @param delay 间隔时间 + * @param isEx true:出现异常继续执行;false:则出现异常跳出执行。 + */ + public static void ofNeverStopAsync(Runnable run, Duration delay, boolean isEx) { + CompletableFuture.runAsync(() -> ofNeverStop(run, delay, isEx), GlobalThreadPool.getExecutor()); + } + +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java new file mode 100644 index 000000000..7b2629ed6 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java @@ -0,0 +1,91 @@ +package org.dromara.hutool.core.util; + +import org.dromara.hutool.core.thread.RetryableTask; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public class RetryUtilTest { + + @Test + void test() { + //自定义根据异常重试 + CompletableFuture> task = RetryableTask.retryForExceptions(() -> { + System.out.println("1231231"); + int a = 1 / 0; + return "qqqq"; + }, ArithmeticException.class) + .delay(Duration.ofSeconds(1)) + .maxAttempts(3) + .asyncExecute(); + + Assertions.assertFalse(task.isDone()); + Assertions.assertEquals("兜底", task.join().get().orElseGet(() -> { + return "兜底"; + })); + Assertions.assertTrue(task.isDone()); + + } + + @Test + public void noReturnTest() { + //根据异常重试,没有返回值 + RetryUtil.ofException( + () -> { + System.out.println(123); + int a = 1 / 0; + }, + 3, + Duration.ofSeconds(1), + () -> { + System.out.println("兜底"); + }, + ArithmeticException.class + ); + } + + + @Test + public void hasReturnTest() { + //根据自定义策略重试 + String result = RetryUtil.ofPredicate( + () -> { + System.out.println(123); +// int a = 1 / 0; + return "ok"; + }, + 5, + Duration.ofSeconds(1), + () -> { + System.out.println("兜底"); + return "do"; + }, + (r, e) -> { + System.out.println("r = " + r); + System.out.println("e = " + e); + return r.equals("ok"); + } + ); + + Assertions.assertEquals("ok", result); + } + + + @Test + public void neverStop() { + //异步一直执行 + RetryUtil.ofNeverStopAsync(() -> { + System.out.println("async -->"); + }, Duration.ofSeconds(1), true); + + System.out.println(" ================ "); + //同步一直执行 + RetryUtil.ofNeverStop(() -> { + System.out.println(123); + }, Duration.ofSeconds(3), true); + + + } +} From e925b5ed7456011411776137a423c7f04157a501 Mon Sep 17 00:00:00 2001 From: kongweiguang Date: Tue, 20 Jun 2023 19:16:18 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E6=97=A0=E9=99=90?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/hutool/core/util/RetryUtil.java | 36 ++----------------- .../hutool/core/util/RetryUtilTest.java | 16 --------- 2 files changed, 3 insertions(+), 49 deletions(-) diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java index 37e482e2a..d76c0d6fc 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java @@ -7,6 +7,9 @@ import org.dromara.hutool.core.thread.ThreadUtil; import java.time.Duration; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.BiPredicate; import java.util.function.Supplier; @@ -112,37 +115,4 @@ public class RetryUtil { .orElseGet(recover); } - - /** - * 从不停止的执行方法 - * - * @param run 执行方法 - * @param delay 间隔时间 - * @param isEx true:出现异常继续执行;false:则出现异常跳出执行。 - */ - public static void ofNeverStop(Runnable run, Duration delay, boolean isEx) { - while (true) { - try { - run.run(); - } catch (Throwable e) { - if (!isEx) { - break; - } - } finally { - ThreadUtil.sleep(delay.toMillis()); - } - } - } - - /** - * 从不停止的执行方法,异步执行 - * - * @param run 执行方法 - * @param delay 间隔时间 - * @param isEx true:出现异常继续执行;false:则出现异常跳出执行。 - */ - public static void ofNeverStopAsync(Runnable run, Duration delay, boolean isEx) { - CompletableFuture.runAsync(() -> ofNeverStop(run, delay, isEx), GlobalThreadPool.getExecutor()); - } - } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java index 7b2629ed6..cda9d370e 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java @@ -72,20 +72,4 @@ public class RetryUtilTest { Assertions.assertEquals("ok", result); } - - @Test - public void neverStop() { - //异步一直执行 - RetryUtil.ofNeverStopAsync(() -> { - System.out.println("async -->"); - }, Duration.ofSeconds(1), true); - - System.out.println(" ================ "); - //同步一直执行 - RetryUtil.ofNeverStop(() -> { - System.out.println(123); - }, Duration.ofSeconds(3), true); - - - } }