• 我人都傻了,CompletableFuture和OpenFegin一起使用竟然报错


    起因

    在一个迭代的需求中,需要记录用户的行为,想着记录用户行为这部分业务逻辑可以修改为异步执行,就使用了CompletableFuture的runAsync()方法实现异步,本来本地环境自己测试也没报错,可是发到开发环境服务器上的时候却报错了。

    报错伪代码

    1. CompletableFuture.runAsync(()->{
    2. //掉用user服务保存用户行为
    3. userClient.saveUserBehavior();
    4. });
    5. 复制代码

    错误信息以及使用的环境

    环境:jdk使用的是openjdk11,SpringCloud版本是2020.0.3

    1. java.lang.ClassNotFoundException: org.springframework.boot.autoconfigure.condition.OnPropertyCondition
    2. 复制代码

    问题排除过程

    因为本地环境是没有报错的,但是开发环境却报错了,所以一开始检查代码时候没有合并,是否没有将代码提交到开发环境。但是重新检查之后,发现代码是已经合并并且提交到开发分支。随后开始分析报错原因,看到这个错误的时候,相信大家和我一样第一反应会认为是Jar包冲突了,但是在仔细检查依赖的Spring的版本,发现Spring的版本也并没有冲突。想着难道是我改的代码改出问题了?我慌了,我也只是把代码修改成了异步啊,于是乎将代码改回同步,果然就没有问题了。

    尝试寻找产生问题的原因

    虽然问题解决了,但是我是一个有着好奇心的程序员,为什么在CompletableFuture掉用Fegin接口会报错,是什么原因导致的?带着这样的疑问,我开始在GitHub上逛了起来。心想这样使用的人应该不止我一个吧,所以应该也有不少同学也遇到了这样的问题吧。果然github没有让我失望,上面真的有我想要的东西。(所以程序员要多在Github上摸鱼)

    在这个issues中,我找到了答案,issues的提交者和我遇到的问题一样,甚至连报错信息都那么的像,我顿时心里乐开了花。总的来说就是Fegin接口是懒加载的,只有在我们第一次使用该Fegin接口的时候才会对Fegin接口进行初始化,但是如果在ForkJoinWorkerThread中使用Fegin接口的话,就会出现ClassNotFoundException。使用其他普通的JVM线程池是不会出现问题的。

    接下来我们看下Spring Cloud团队对于这个问题的回答

    Spring Cloud团队的人给出的回答是这样的:

    大概说的就是在并行流(多线程)中调用 FeignBlockingLoadBalancerClient时会出现 ClassNotFoundException。

    但是经过在本地测试,只有在使用ForkJoinPool线程池中调用Fegin接口才会报错,如果我们在ThreadPoolExecutor自定义线程池或者使用jdk提供的线程池(例如FixedThreadPool)中调用Fegin接口是不会报错的,而恰巧CompletableFuture中的默认线程池也是通过ForkJoinPool线程池来实现的。难道真的有这么巧吗?这两位开发者证实了在ForkJoinPool中使用Fegin接口会出现这个错误,但是其他JVM线程池是可以正常运行的。

    那为什么在本地环境测试没有问题,但是发布到dev环境却出现了问题呢?

    带着这个疑问我检查了开发环境与本地环境的差异,发现本地环境使用的JDk版本是11,但是dev环境使用的JDK是Openjdk11。随后我在本地替换jdk的版本为openJdk,在本地进行进一步测试。

    果然代码再次报错。

    1. java.lang.ClassNotFoundException: org.springframework.boot.autoconfigure.condition.OnPropertyCondition
    2. at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source) ~[na:na]
    3. at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source) ~[na:na]
    4. at java.base/java.lang.ClassLoader.loadClass(Unknown Source) ~[na:na]
    5. at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
    6. at java.base/java.lang.Class.forName(Unknown Source) ~[na:na]
    7. 复制代码

    那么这个问题Spring Cloud团队解决了吗?

    很遗憾,并没有解决。Spring Cloud团队认为这个问题的优先级并不高,并且不推荐在CompletableFuture中调用Fegin接口,还表示如果社区有人愿意为这个问题提交代码,Spring Cloud团队也会认真审核代码,如果通过的话也会合并代码。

    好吧,那看来我只能使用自定义TreadPool来实现异步了(当然也可以使用mq)。

    总结

    只有在openJDK11版本下才会出现这个问题(回家后我也试过JDK8,并不会出现异常),并且如果在ForkJoinPool中调用Fegin接口,那么就会出现ClassNotFoundException。因为Spring Cloud团队认为这个问题的优先级不高,并没有解决这个问题。虽然CompletableFuture真的很好用,但是如果你需要在CompletableFuture中调用Fegin接口,那么建议定义一个自定义的线程池,不要使用默认的ForkJoinPool.

    使用下面这样的api,CompletableFuture就会使用我们传入的线程池去执行任务。

    1. public static CompletableFuture<Void> runAsync(Runnable runnable,
    2. Executor executor) {
    3. return asyncRunStage(screenExecutor(executor), runnable);
    4. }
    5. 复制代码

    所以小伙伴们如果需要异步调用Fegin接口,还需要多加注意呀,避免踩坑。

    如果有任何疑问,欢迎在下方评论区留言。最后,原创不易,如果本文对你有所帮助,那么点个赞再走吧。

  • 相关阅读:
    Java中如何实现定时任务?
    【Maven】Jar发布到maven中心仓库的步骤
    概率论与数据统计学习:随机变量(一)——知识总结与C语言案例实现
    气象站:处暑至热未止,从事不同行业的人们应该如何预知天气变化
    桂电人工智能学院大数据实验,使用 Docker 搭建 hadoop 集群
    园子周边第2季:更大的鼠标垫,没有logo的鼠标垫
    X3E伺服转矩模式参数设置
    思维导图:定时器设计
    代码审计——任意文件下载详解(二)
    Git暂存区的意义或git add的意义
  • 原文地址:https://blog.csdn.net/java_beautiful/article/details/126013348