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/thread/RetryUtil.java similarity index 67% rename from hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java rename to hutool-core/src/main/java/org/dromara/hutool/core/thread/RetryUtil.java index d76c0d6fc..f55fdb689 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/util/RetryUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/thread/RetryUtil.java @@ -1,15 +1,20 @@ -package org.dromara.hutool.core.util; +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.thread; 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.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.function.BiPredicate; import java.util.function.Supplier; @@ -33,8 +38,8 @@ public class RetryUtil { * @param recover 达到最大重试次数后执行的备用方法,入参是重试过程中的异常 * @param exs 指定的异常类型需要重试 */ - @SafeVarargs - public static void ofException(Runnable run, long maxAttempts, Duration delay, Runnable recover, Class... exs) { + public static void ofException(final Runnable run, final long maxAttempts, final Duration delay, + final Runnable recover, Class... exs) { if (ArrayUtil.isEmpty(exs)) { exs = ArrayUtil.append(exs, RuntimeException.class); } @@ -61,8 +66,8 @@ public class RetryUtil { * @param 结果类型 * @return 执行结果 */ - @SafeVarargs - public static T ofException(Supplier sup, long maxAttempts, Duration delay, Supplier recover, Class... exs) { + public static T ofException(final Supplier sup, final long maxAttempts, final Duration delay, + final Supplier recover, Class... exs) { if (ArrayUtil.isEmpty(exs)) { exs = ArrayUtil.append(exs, RuntimeException.class); } @@ -84,7 +89,8 @@ public class RetryUtil { * @param recover 达到最大重试次数后执行的备用方法,入参是重试过程中的异常 * @param predicate 自定义重试条件 */ - public static void ofPredicate(Runnable run, long maxAttempts, Duration delay, Supplier recover, BiPredicate predicate) { + public static void ofPredicate(final Runnable run, final long maxAttempts, final Duration delay, + final Supplier recover, final BiPredicate predicate) { RetryableTask.retryForPredicate(run, predicate) .delay(delay) .maxAttempts(maxAttempts) @@ -106,7 +112,8 @@ public class RetryUtil { * @param 结果类型 * @return 执行结果 */ - public static T ofPredicate(Supplier sup, long maxAttempts, Duration delay, Supplier recover, BiPredicate predicate) { + public static T ofPredicate(final Supplier sup, final long maxAttempts, final Duration delay, + final Supplier recover, final BiPredicate predicate) { return RetryableTask.retryForPredicate(sup, predicate) .delay(delay) .maxAttempts(maxAttempts) 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 index d2d816561..92c45156e 100644 --- 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 @@ -14,10 +14,80 @@ import static java.util.Objects.nonNull; /** * 重试任务类 * + * @param 任务结果类型 * @author kongweiguang * @since 6.0.0 */ public class RetryableTask { + + // region ----- retryFor + + /** + * 重试根据指定的异常,没有返回值 + * + * @param 返回值类型 + * @param run 执行的方法 {@link Runnable} + * @param ths 指定异常 {@link Throwable},匹配任意一个异常时重试 + * @return 当前对象 + */ + @SafeVarargs + public static RetryableTask retryForExceptions(final Runnable run, final Class... ths) { + return retryForExceptions(() -> { + run.run(); + return null; + }, ths); + } + + /** + * 重试根据指定的异常,有返回值 + * + * @param 返回值类型 + * @param sup 执行的方法 {@link Supplier} + * @param ths 指定异常 {@link Throwable},匹配任意一个异常时重试 + * @return 当前对象 + */ + @SafeVarargs + public static RetryableTask retryForExceptions(final Supplier sup, final Class... ths) { + Assert.isTrue(ths.length != 0, "exs cannot be empty"); + + final 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 run 执行的方法 {@link Runnable} + * @param predicate 策略 {@link BiPredicate},返回{@code true}时表示重试 + * @return 当前对象 + */ + public static RetryableTask retryForPredicate(final Runnable run, final BiPredicate predicate) { + return retryForPredicate(() -> { + run.run(); + return null; + }, predicate); + } + + /** + * 重试根据指定的策略,没有返回值 + * + * @param 返回值类型 + * @param sup 执行的方法 {@link Supplier} + * @param predicate 策略 {@link BiPredicate},返回{@code true}时表示重试 + * @return 当前对象 + */ + public static RetryableTask retryForPredicate(final Supplier sup, final BiPredicate predicate) { + return new RetryableTask<>(sup, predicate); + } + // endregion + /** * 执行结果 */ @@ -43,9 +113,9 @@ public class RetryableTask { * 构造方法,内部使用,调用请使用请用ofXXX * * @param sup 执行的方法 - * @param predicate 策略 {@link BiPredicate} + * @param predicate 策略 {@link BiPredicate},返回{@code true}时表示重试 */ - private RetryableTask(Supplier sup, BiPredicate predicate) { + private RetryableTask(final Supplier sup, final BiPredicate predicate) { Assert.notNull(sup, "task parameter cannot be null"); Assert.notNull(predicate, "predicate parameter cannot be null"); @@ -53,81 +123,13 @@ public class RetryableTask { 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} + * @return 当前对象 */ - public RetryableTask maxAttempts(long maxAttempts) { + public RetryableTask maxAttempts(final long maxAttempts) { Assert.isTrue(this.maxAttempts > 0, "maxAttempts must be greater than 0"); this.maxAttempts = maxAttempts; @@ -138,9 +140,9 @@ public class RetryableTask { * 重试间隔时间 * * @param delay 间隔时间 - * @return 当前对象 {@link RetryableTask} + * @return 当前对象 */ - public RetryableTask delay(Duration delay) { + public RetryableTask delay(final Duration delay) { Assert.notNull(this.delay, "delay parameter cannot be null"); this.delay = delay; @@ -168,7 +170,7 @@ public class RetryableTask { /** * 同步执行重试方法 * - * @return 当前对象 {@link RetryableTask} + * @return 当前对象 */ public RetryableTask execute() { return doExecute(); @@ -177,25 +179,25 @@ public class RetryableTask { /** * 开始重试 * - * @return 当前对象 {@link RetryableTask} + * @return 当前对象 **/ private RetryableTask doExecute() { Throwable th = null; while (--this.maxAttempts >= 0) { - try { this.result = this.sup.get(); - } catch (Throwable t) { + } catch (final Throwable t) { th = t; - } finally { - //判断重试 - if (this.predicate.test(this.result, th)) { - ThreadUtil.sleep(delay.toMillis()); - } else { - break; - } } + + //判断重试 + if (!this.predicate.test(this.result, th)) { + // 条件不满足时,跳出 + break; + } + + ThreadUtil.sleep(delay.toMillis()); } return this; diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/thread/RetryUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/thread/RetryUtilTest.java new file mode 100644 index 000000000..d635dc137 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/thread/RetryUtilTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.thread; + +import org.dromara.hutool.core.lang.Console; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +@SuppressWarnings({"NumericOverflow", "divzero"}) +public class RetryUtilTest { + + @Test + @Disabled + void test() { + //自定义根据异常重试 + final CompletableFuture> task = RetryableTask.retryForExceptions(() -> { + Console.log("1231231"); + final int a = 1 / 0; + return String.valueOf(a); + }, ArithmeticException.class) + .delay(Duration.ofSeconds(1)) + .maxAttempts(3) + .asyncExecute(); + + Assertions.assertFalse(task.isDone()); + Assertions.assertEquals("兜底", task.join().get().orElseGet(() -> "兜底")); + Assertions.assertTrue(task.isDone()); + + } + + @SuppressWarnings("unchecked") + @Test + @Disabled + public void noReturnTest() { + //根据异常重试,没有返回值 + RetryUtil.ofException( + () -> { + Console.log(123); + final int a = 1 / 0; + Console.log(a); + }, + 3, + Duration.ofSeconds(1), + () -> { + Console.log("兜底"); + }, + ArithmeticException.class + ); + } + + + @Test + @Disabled + public void hasReturnTest() { + //根据自定义策略重试 + final String result = RetryUtil.ofPredicate( + () -> { + Console.log(123); +// int a = 1 / 0; + return "ok"; + }, + 5, + Duration.ofSeconds(1), + () -> { + Console.log("兜底"); + return "do"; + }, + (r, e) -> { + Console.log("r = " + r); + Console.log("e = " + e); + return r.equals("ok"); + } + ); + + Assertions.assertEquals("ok", result); + } + +} 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 deleted file mode 100644 index cda9d370e..000000000 --- a/hutool-core/src/test/java/org/dromara/hutool/core/util/RetryUtilTest.java +++ /dev/null @@ -1,75 +0,0 @@ -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); - } - -}