Skip to content
/ cffu Public

🦝 Java CompletableFuture-Fu(CF-Fu, pronounced "Shifu"), a tiny sidekick library to make CompletableFuture usage more convenient, more efficient and safer in your application. 😋🚀🦺

License

Notifications You must be signed in to change notification settings

foldright/cffu

Repository files navigation

🦝 CompletableFuture-Fu(CF-Fu)

Github Workflow Build Status Github Workflow Build Status Codecov Java support Kotlin License Javadocs dokka Maven Central GitHub Releases GitHub Stars GitHub Forks GitHub Issues GitHub Contributors GitHub repo size gitpod: Ready to Code

👉 cffuCompletableFuture-Fu 🦝)是一个小小的CompletableFuture(CF)辅助增强库,提升CF使用体验并减少误用,在业务中更方便高效安全地使用CF。😋🚀🦺

欢迎 👏 💖

shifu



🔧 功能

提供的功能有:

  • ☘️ 补全业务使用中缺失的功能
    • 🏪 更方便的功能,如
      • 支持返回多个CF的运行结果,而不是不返回CF的运行结果(allOf
        如方法allResultsFailFastOf / allResultsOf / mSupplyFailFastAsync / thenMApplyFailFastAsync
      • 支持返回多个不同类型的CF结果,而不是同一类型
        如方法allTupleFailFastOf / allTupleOf / mSupplyTupleFailFastAsync / thenMApplyTupleFailFastAsync
      • 支持直接运行多个Action,而不是要先包装成CompletableFuture
        如方法mSupplyTupleFailFastAsync / mSupplyMostSuccessAsync / thenMApplyTupleFailFastAsync / thenMRunFailFastAsync
      • 支持处理指定异常类型的catching方法,而不是处理所有异常Throwableexceptionally
    • 🚦 更高效灵活的并发执行策略,如
      • AllFailFast策略:当输入的多个CF有失败时快速失败返回,而不再于事无补地等待所有CF运行完成(allOf
      • AnySuccess策略:返回首个成功的CF结果,而不是首个完成(但可能失败)的CFanyOf
      • AllSuccess策略:返回多个CF中成功的结果,对于失败的CF返回指定的缺省值
      • MostSuccess策略:指定时间内返回多个CF中成功的结果,对于失败或超时的CF返回指定的缺省值
      • All(Complete) / Any(Complete)策略:这2个是CompletableFuture已有支持的策略
    • 🦺 更安全的使用方式,如
      • 支持设置缺省的业务线程池并封装携带,CffuFactory#builder(executor)方法
      • 支持超时的join的方法,join(timeout, unit)方法
      • 超时执行安全的cffuOrTimeout / cffuCompleteOnTimeout方法
      • 一定不会修改CF结果的peek处理方法
        whenComplete方法可能会修改CF的结果,返回的CF与输入CF并不一定一致)
      • 支持禁止强制篡改,CffuFactoryBuilder#forbidObtrudeMethods方法
      • 在类方法附加完善的代码质量注解,在编码时IDE能尽早提示出问题
        @NonNull@Nullable@CheckReturnValue@Contract
    • 🧩 缺失的基础基本功能,除了上面面向安全而新实现方法(如join(timeout, unit) / cffuOrTimeout / peek),还有
      • 异步异常完成,completeExceptionallyAsync方法
      • 非阻塞地获取成功结果,如果CF失败或还在运行中则返回指定的缺省值,getSuccessNow方法
      • 解包装CF异常成业务异常,unwrapCfException方法
  • 💪 已有功能的增强,如
    • anyOf方法:返回具体类型T(类型安全),而不是返回ObjectCompletableFuture#anyOf
    • allof / anyOf方法:输入更宽泛的CompletionStage参数类型,而不是CompletableFuture类(CompletableFuture#allOf/anyOf
  • Backport支持Java 8Java 9+高版本的所有CF新功能方法在Java 8低版本直接可用,如
    • 超时控制:orTimeout / completeOnTimeout
    • 延迟执行:delayedExecutor
    • 工厂方法:failedFuture / completedStage / failedStage
    • 处理操作:completeAsync / exceptionallyAsync / exceptionallyCompose / copy
    • 非阻塞读:resultNow / exceptionNow / state
  • 🍩 一等公民支持Kotlin

更多cffu的使用方式与功能说明详见 User Guide

关于CompletableFuture

如何管理并发执行是个复杂易错的问题,业界有大量的工具、框架可以采用。

并发工具、框架的广度了解,可以看看如《七周七并发模型》、《Java虚拟机并发编程》、《Scala并发编程(第2版)》;更多关于并发主题的书籍参见书单

其中CompletableFuture(CF)有其优点:

  • Java标准库内置
    • 无需额外依赖,几乎总是可用
    • 相信有极高的实现质量
  • 广为人知广泛使用,有一流的群众基础
    • CompletableFuture在2014年发布的Java 8提供,有10年了
    • CompletableFuture的父接口Future早在2004年发布的Java 5中提供,有20年了
    • 虽然Future接口不支持 运行结果的异步获取与并发执行逻辑的编排,但也让广大Java开发者熟悉了Future这个典型的概念与工具
  • 功能强大、但不会非常庞大复杂
    • 足以应对日常的业务需求开发
    • 其它的大型并发框架(比如AkkaRxJava)在使用上需要理解的内容要多很多
    • 当然基本的并发关注方面及其复杂性,与具体使用哪个工具无关,都是要理解与注意的
  • 高层抽象

和其它并发工具、框架一样,CompletableFuture用于

  • 并发执行业务逻辑,或说编排并发的处理流程/处理任务
  • 多核并行处理,充分利用资源
  • 提升业务响应性

值得更深入地了解和应用。 💕

👥 User Guide

1. cffu的三种使用方式

cffu支持三种使用方式:

  • 🦝 1) 使用Cffu
    • 项目使用Java语言时,推荐这种使用方式
    • 直接使用CompletableFuture类的代码可以比较简单的迁移到Cffu类,包含2步修改:
    • 依赖io.foldright:cffu
  • 🛠️️ 2) 使用CompletableFutureUtils工具类
    • 如果你不想在项目中引入新类(Cffu类)、觉得这样增加了复杂性的话,
      • 完全可以把cffu库作为一个工具类来用
      • 优化CompletableFuture使用的工具方法在业务项目中很常见,CompletableFutureUtils提供了一系列实用可靠高效安全的工具方法
    • 这种使用方式有些cffu功能没有提供(也没有想到实现方案) 😔
      如支持设置缺省的业务线程池、禁止强制篡改
    • 依赖io.foldright:cffu
  • 🍩 3) 使用Kotlin扩展方法
    • 项目使用Kotlin语言时,推荐这种使用方式
    • 要依赖io.foldright:cffu-kotlin

更多参见cffu三种使用方式的示例代码。 🎪

2. cffu功能介绍

2.1 返回多个CF的整体运行结果

CompletableFutureallOf方法不返回多个CF的运行结果(方法返回类型是CF<Void>)。

不能方便地获取多个CF的运行结果:

  • 需要在allOf方法之后再通过入参CF的读操作(如join / get)来获取结果
  • 或是在传入的CompletableFuture Action中设置外部的变量,需要注意多线程读写的线程安全问题 ⚠️
    • 多线程读写涉及多线程数据传递的复杂性,遗漏并发逻辑的数据读写的正确处理是业务代码中的常见问题❗️
    • 并发深坑勿入,并发逻辑复杂易出Bug 🐞
      如果涉及超时则会更复杂,JDK CompletableFuture自身在Java 21中也有这方面的Bug修复

cffuallResultsFailFastOf / allResultsOf / mostSuccessResultsOf等方法提供了返回多个CF运行结果的功能。

使用这些方法获取多个CF的整体运行结果:

  • 方便直接
  • 因为返回的是有整体结果的CF,可以继续串接非阻塞的操作,所以自然减少了阻塞读方法(如join / get)的使用,尽量降低业务逻辑的死锁风险
  • 规避了在业务逻辑中直接实现多线程读写逻辑的复杂线程安全问题与逻辑错误
  • 使用「可靠实现与测试的」库的并发功能而不是去直接实现 是 最佳实践 🌟

示例代码如下:

public class AllResultsOfDemo {
  public static final Executor myBizExecutor = Executors.newCachedThreadPool();
  public static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();

  public static void main(String[] args) throws Exception {
    //////////////////////////////////////////////////
    // CffuFactory#allResultsOf
    //////////////////////////////////////////////////
    Cffu<Integer> cffu1 = cffuFactory.completedFuture(21);
    Cffu<Integer> cffu2 = cffuFactory.completedFuture(42);

    Cffu<Void> all = cffuFactory.allOf(cffu1, cffu2);
    // Result type is Void!
    //
    // the result can be got by input argument `cf1.get()`, but it's cumbersome.
    // so we can see a lot of util methods to enhance `allOf` with result in our project.

    Cffu<List<Integer>> allResults = cffuFactory.allResultsOf(cffu1, cffu2);
    System.out.println(allResults.get());

    //////////////////////////////////////////////////
    // or CompletableFutureUtils#allResultsOf
    //////////////////////////////////////////////////
    CompletableFuture<Integer> cf1 = CompletableFuture.completedFuture(21);
    CompletableFuture<Integer> cf2 = CompletableFuture.completedFuture(42);

    CompletableFuture<Void> all2 = CompletableFuture.allOf(cf1, cf2);
    // Result type is Void!

    CompletableFuture<List<Integer>> allResults2 = allResultsOf(cf1, cf2);
    System.out.println(allResults2.get());
  }
}

# 完整可运行的Demo代码参见AllResultsOfDemo.java

上面是多个相同结果类型的CFcffu还提供了返回多个不同类型CF结果的allTupleFailFastOf / allTupleOf / mSupplyTupleFailFastAsync等方法。

示例代码如下:

public class AllTupleOfDemo {
  public static final Executor myBizExecutor = Executors.newCachedThreadPool();
  public static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();

  public static void main(String[] args) throws Exception {
    //////////////////////////////////////////////////
    // allTupleFailFastOf / allTupleOf
    //////////////////////////////////////////////////
    Cffu<String> cffu1 = cffuFactory.completedFuture("21");
    Cffu<Integer> cffu2 = cffuFactory.completedFuture(42);

    Cffu<Tuple2<String, Integer>> allTuple = cffuFactory.allTupleFailFastOf(cffu1, cffu2);
    System.out.println(allTuple.get());

    //////////////////////////////////////////////////
    // or CompletableFutureUtils.allTupleFailFastOf / allTupleOf
    //////////////////////////////////////////////////
    CompletableFuture<String> cf1 = CompletableFuture.completedFuture("21");
    CompletableFuture<Integer> cf2 = CompletableFuture.completedFuture(42);

    CompletableFuture<Tuple2<String, Integer>> allTuple2 = allTupleFailFastOf(cf1, cf2);
    System.out.println(allTuple2.get());
  }
}

# 完整可运行的Demo代码参见AllTupleOfDemo.java

2.2 支持设置缺省的业务线程池并封装携带

  • CompletableFuture异步执行(即CompletableFuture*Async方法),使用的缺省线程池是ForkJoinPool.commonPool()
  • 这个线程池差不多是CPU个线程,合适执行CPU密集的任务;对于业务逻辑,往往有很多等待操作(如网络IO、阻塞等待),并不是CPU密集的。
  • 业务使用这个缺省线程池ForkJoinPool.commonPool()是很危险的❗

结果就是,

  • 在业务逻辑中,调用CompletableFuture*Async方法时,几乎每次都要反复传入指定的业务线程池;这让CompletableFuture的使用很繁琐易错 🤯
  • 在底层逻辑中,当底层操作回调业务时(如RPC回调),不合适或方便为业务提供线程池;这时使用Cffu封装携带的线程池既方便又合理安全
    这个使用场景更多可以看看CompletableFuture原理与实践 - 4.2.3 异步RPC调用注意不要阻塞IO线程池

示例代码如下:

public class NoDefaultExecutorSettingForCompletableFuture {
  public static final Executor myBizExecutor = Executors.newCachedThreadPool();

  public static void main(String[] args) {
    CompletableFuture<Void> cf1 = CompletableFuture.runAsync(
        () -> System.out.println("doing a long time work!"),
        myBizExecutor);

    CompletableFuture<Void> cf2 = CompletableFuture
        .supplyAsync(
            () -> {
              System.out.println("doing another long time work!");
              return 42;
            },
            myBizExecutor)
        .thenAcceptAsync(
            i -> System.out.println("doing third long time work!"),
            myBizExecutor);

    CompletableFuture.allOf(cf1, cf2).join();
  }
}

# 完整可运行的Demo代码参见NoDefaultExecutorSettingForCompletableFuture.java

Cffu支持设置缺省的业务线程池,规避上面的繁琐与危险。示例代码如下:

public class DefaultExecutorSettingForCffu {
  public static final Executor myBizExecutor = Executors.newCachedThreadPool();
  public static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();

  public static void main(String[] args) {
    Cffu<Void> cf1 = cffuFactory.runAsync(() -> System.out.println("doing a long time work!"));

    Cffu<Void> cf2 = cffuFactory.supplyAsync(() -> {
      System.out.println("doing another long time work!");
      return 42;
    }).thenAcceptAsync(i -> System.out.println("doing third long time work!"));

    cffuFactory.allOf(cf1, cf2).join();
  }
}

# 完整可运行的Demo代码参见DefaultExecutorSettingForCffu.java

2.3 高效灵活的并发执行策略(AllFailFast / AnySuccess / AllSuccess / MostSuccess

  • CompletableFutureallOf方法会等待所有输入CF运行完成;即使有CF失败了也要等待后续CF都运行完成,再返回一个失败的CF
    • 对于业务逻辑来说,这样失败且继续等待策略,减慢了业务响应性;会希望如果有输入CF失败了,则快速失败不再做于事无补的等待
    • cffu提供了相应的allResultsFailFastOf等方法
    • allOf / allResultsFailFastOf两者都是,只有当所有的输入CF都成功时,才返回成功结果
  • CompletableFutureanyOf方法返回首个完成的CF(不会等待后续没有完成的CF,赛马模式);即使首个完成的CF是失败的,也会返回这个失败的CF结果。
    • 对于业务逻辑来说,会希望赛马模式返回首个成功的CF结果,而不是首个完成但失败的CF
    • cffu提供了相应的anySuccessOf等方法
    • anySuccessOf只有当所有的输入CF都失败时,才返回失败结果
  • 返回多个CF中成功的结果,对于失败的CF返回指定的缺省值
    • 业务有容错逻辑时,当某些CF处理出错时可以使用成功的那部分结果,而不是整体处理失败
    • cffu提供了相应的allSuccessOf等方法
  • 返回指定时间内多个CF中成功的结果,对于失败或超时的CF返回指定的缺省值
    • 业务最终一致性时,能返回就尽量返回有的;对于没有及时返回还在运行中处理的CF,结果会写到分布式缓存中避免重复计算,下次业务请求就有了
    • 这是个常见业务使用模式,cffu提供了相应的mostSuccessResultsOf等方法

📔 关于多个CF的并发执行策略,可以看看JavaScript规范Promise Concurrency;在JavaScript中,Promise即对应CompletableFuture

JavaScript Promise提供了4个并发执行方法:

  • Promise.all():等待所有Promise运行成功,只要有一个失败就立即返回失败(对应cffuallResultsFailFastOf方法)
  • Promise.allSettled():等待所有Promise运行完成,不管成功失败(对应cffuallResultsOf方法)
  • Promise.any():赛马模式,立即返回首个成功的Promise(对应cffuanySuccessOf方法)
  • Promise.race():赛马模式,立即返回首个完成的Promise(对应cffuanyOf方法)

PS:JavaScript Promise的方法命名真考究~ 👍

cffu新加2个方法后,对齐了JavaScript Promise规范的并发方法~ 👏

示例代码如下:

public class ConcurrencyStrategyDemo {
  public static final Executor myBizExecutor = Executors.newCachedThreadPool();
  public static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();

  public static void main(String[] args) throws Exception {
    ////////////////////////////////////////////////////////////////////////
    // CffuFactory#allResultsFailFastOf
    // CffuFactory#anySuccessOf
    // CffuFactory#mostSuccessResultsOf
    ////////////////////////////////////////////////////////////////////////
    final Cffu<Integer> successAfterLongTime = cffuFactory.supplyAsync(() -> {
      sleep(3000); // sleep LONG time
      return 42;
    });
    final Cffu<Integer> failed = cffuFactory.failedFuture(new RuntimeException("Bang!"));

    Cffu<List<Integer>> failFast = cffuFactory.allResultsFailFastOf(successAfterLongTime, failed);
    // fail fast without waiting successAfterLongTime
    System.out.println(failFast.exceptionNow());

    Cffu<Integer> anySuccess = cffuFactory.anySuccessOf(successAfterLongTime, failed);
    System.out.println(anySuccess.get());

    Cffu<List<Integer>> mostSuccess = cffuFactory.mostSuccessResultsOf(
        0, 100, TimeUnit.MILLISECONDS, successAfterLongTime, failed);
    System.out.println(mostSuccess.get());

    ////////////////////////////////////////////////////////////////////////
    // or CompletableFutureUtils#allResultsFailFastOf
    //    CompletableFutureUtils#anySuccessOf
    //    CompletableFutureUtils#mostSuccessResultsOf
    ////////////////////////////////////////////////////////////////////////
    final CompletableFuture<Integer> successAfterLongTimeCf = CompletableFuture.supplyAsync(() -> {
      sleep(3000); // sleep LONG time
      return 42;
    });
    final CompletableFuture<Integer> failedCf = failedFuture(new RuntimeException("Bang!"));

    CompletableFuture<List<Integer>> failFast2 = allResultsFailFastOf(successAfterLongTimeCf, failedCf);
    // fail fast without waiting successAfterLongTime
    System.out.println(exceptionNow(failFast2));

    CompletableFuture<Integer> anySuccess2 = anySuccessOf(successAfterLongTimeCf, failedCf);
    System.out.println(anySuccess2.get());

    CompletableFuture<List<Integer>> mostSuccess2 = mostSuccessResultsOf(
        0, 100, TimeUnit.MILLISECONDS, successAfterLongTime, failed);
    System.out.println(mostSuccess2.get());
  }
}

# 完整可运行的Demo代码参见ConcurrencyStrategyDemo.java

2.4 支持直接运行多个Action,而不是要先包装成CompletableFuture

CompletableFutureallOf/anyOF方法输入的是CompletableFuture,当业务直接有要编排业务逻辑方法时仍然需要先包装成CompletableFuture再运行:

  • 繁琐
  • 也模糊了业务流程

cffu提供了直接运行多个Action的方法,方便直接明了地表达业务编排流程。

示例代码如下:

public class MultipleActionsDemo {
  static void mRunAsyncDemo() {
    // wrap tasks to CompletableFuture first, AWKWARD! 😖
    CompletableFuture.allOf(
        CompletableFuture.runAsync(() -> System.out.println("task1")),
        CompletableFuture.runAsync(() -> System.out.println("task2")),
        CompletableFuture.runAsync(() -> System.out.println("task3"))
    );

    // just run multiple actions, fresh and cool 😋
    CompletableFutureUtils.mRunAsync(
        () -> System.out.println("task1"),
        () -> System.out.println("task2"),
        () -> System.out.println("task3")
    );
  }
}

这些多Action方法也配套实现了「不同的并发执行策略」与「返回多个运行结果」的支持。

示例代码如下:

public class MultipleActionsDemo {
  static void thenMApplyAsyncDemo() {
    // wrap tasks to CompletableFuture first, AWKWARD! 😖
    completedFuture(42).thenCompose(v ->
        CompletableFutureUtils.allResultsFailFastOf(
            CompletableFuture.supplyAsync(() -> v + 1),
            CompletableFuture.supplyAsync(() -> v + 2),
            CompletableFuture.supplyAsync(() -> v + 3)
        )
    ).thenAccept(System.out::println);
    // output: [43, 44, 45]

    // just run multiple actions, fresh and cool 😋
    CompletableFutureUtils.thenMApplyFailFastAsync(
        completedFuture(42),
        v -> v + 1,
        v -> v + 2,
        v -> v + 3
    ).thenAccept(System.out::println);
    // output: [43, 44, 45]
  }
}

# 完整可运行的Demo代码参见MultipleActionsDemo.java

2.5 支持超时的join的方法

cf.join()会「不超时永远等待」,在业务中很危险❗️当意外出现长时间等待时,会导致:

  • 主业务逻辑阻塞,没有机会做相应的处理,以及时响应用户
  • 会费掉一个线程,线程是很有限的资源(一般几百个),耗尽线程意味着服务瘫痪故障

join(timeout, unit)方法即支持超时的join的方法;就像cf.get(timeout, unit) 之于 cf.get()

这个新方法使用简单类似,不附代码示例。

2.6 Backport支持Java 8

Java 9+高版本的所有CF新功能方法在Java 8低版本直接可用。

其中重要的Backport功能有:

  • 超时控制:orTimeout / completeOnTimeout
  • 延迟执行:delayedExecutor
  • 工厂方法:failedFuture / completedStage / failedStage
  • 处理操作:completeAsync / exceptionallyAsync / exceptionallyCompose / copy
  • 非阻塞读:resultNow / exceptionNow / state

这些backport的方法是CompletableFuture的已有功能,不附代码示例。

2.7 返回具体类型的anyOf方法

CompletableFuture.anyOf方法返回类型是Object,丢失具体类型,不类型安全,使用时需要转型也不方便。

cffu提供的anySuccessOf / anyOf方法,返回具体类型T,而不是返回Object

这个方法使用简单类似,不附代码示例。

2.8 输入宽泛类型的allof/anyOf方法

CompletableFuture#allof/anyOf方法输入参数类型是CompletableFuture,而输入更宽泛的CompletionStage类型;对于CompletionStage类型的输入,则需要调用CompletionStage#toCompletableFuture方法做转换。

cffu提供的allof / anyOf方法输入更宽泛的CompletionStage参数类型,使用更方便。

方法使用简单类似,不附代码示例。

更多功能说明

可以参见:

3. 如何从直接使用CompletableFuture类迁移到Cffu

为了使用cffu增强功能,可以迁移已有直接使用CompletableFuture类的代码到Cffu类。包含2步修改:

  • 在类型声明地方,CompletableFuture类改成Cffu
  • CompletableFuture静态方法调用的地方,类名CompletableFuture改成cffuFactory实例

之所以可以这样迁移,是因为:

  • CompletableFuture类的所有实例方法都在Cffu类,且有相同的方法签名与功能
  • CompletableFuture类的所有静态方法都在CffuFactory类,且有相同的方法签名与功能

🔌 API Docs

代码示例:

🍪依赖

可以在 central.sonatype.com 查看最新版本与可用版本列表。

  • cffu库(包含Java CompletableFuture的增强CompletableFutureUtils):
    • For Maven projects:

      <dependency>
        <groupId>io.foldright</groupId>
        <artifactId>cffu</artifactId>
        <version>1.0.0-Alpha27</version>
      </dependency>
    • For Gradle projects:

      // Gradle Kotlin DSL
      implementation("io.foldright:cffu:1.0.0-Alpha27")
      // Gradle Groovy DSL
      implementation 'io.foldright:cffu:1.0.0-Alpha27'
  • cffu Kotlin支持库:
    • For Maven projects:

      <dependency>
        <groupId>io.foldright</groupId>
        <artifactId>cffu-kotlin</artifactId>
        <version>1.0.0-Alpha27</version>
      </dependency>
    • For Gradle projects:

      // Gradle Kotlin DSL
      implementation("io.foldright:cffu-kotlin:1.0.0-Alpha27")
      // Gradle Groovy DSL
      implementation 'io.foldright:cffu-kotlin:1.0.0-Alpha27'
  • cffu bom:
    • For Maven projects:

      <dependency>
        <groupId>io.foldright</groupId>
        <artifactId>cffu-bom</artifactId>
        <version>1.0.0-Alpha27</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    • For Gradle projects:

      // Gradle Kotlin DSL
      implementation(platform("io.foldright:cffu-bom:1.0.0-Alpha27"))
      // Gradle Groovy DSL
      implementation platform('io.foldright:cffu-bom:1.0.0-Alpha27')
  • 📌 TransmittableThreadLocal(TTL)cffu executor wrapper SPI实现
    • For Maven projects:

      <dependency>
        <groupId>io.foldright</groupId>
        <artifactId>cffu-ttl-executor-wrapper</artifactId>
        <version>1.0.0-Alpha27</version>
        <scope>runtime</scope>
      </dependency>
    • For Gradle projects:

      // Gradle Kotlin DSL
      runtimeOnly("io.foldright:cffu-ttl-executor-wrapper:1.0.0-Alpha27")
      // Gradle Groovy DSL
      runtimeOnly 'io.foldright:cffu-ttl-executor-wrapper:1.0.0-Alpha27'

📚 更多资料

👋 关于库名

cffuCompletableFuture-Fu的缩写;读作C Fu,谐音Shifu/师傅

嗯嗯,想到了《功夫熊猫》里可爱的小浣熊师傅吧~ 🦝

shifu