• CompletableFuture idea执行与springboot打包后 类加载器 不同 导致类加载错误


    1.idea运行java程序分析

    在idea点击运行按钮运行程序,实际上idea是javac编译后用java命令classpath参数把依赖路径全部都加入命令行
    在这里插入图片描述

    java.exe .... -classpath   D:\jcode\new-version\service\recommend\target\classes;
    E:\reposity\org\springframework\boot\spring-boot-starter-web\2.6.3\spring-boot-starter-web-2.6.3.jar;.... .....    类名
    
    • 1
    • 2

    在这里插入图片描述
    它的所有依赖类加载器都是使用的是TomcatEmbeddedWebappClassLoader,他的父加载器是AppClassLoader(它可以加载classpath路径下的类)
    加载器内容可以看:https://blog.51cto.com/u_14518853/4893903
    在这里插入图片描述

    2.CompletableFuture的ForkjoinPool线程池

    对于CompletableFuture来说,如果用户使用的时候没有手动设置线程池,那么CompletableFuture默认会使用ForkJoinPool
    在这里插入图片描述
    ForJoinPool初始化的时候有一个默认的线程工厂
    在这里插入图片描述

    在默认的线程工厂中设置了创建线程的时候使用Application ClassLoader(应用程序类加载器) 可以加载classpath下的类
    在这里插入图片描述
    在这里插入图片描述
    所以在idea中运行 CompletableFuture 加载类通过双亲委派机制都是没有问题的

    3.springboot打包后类加载器的变化

    通过springboot的maven插件打包后,类加载器如下:

    TomcatEmbeddedWebappClassLoader-->LaunchedURLClassLoader
    -->AppClassLoader-->ExtClassLoader-->BootstrapClassLoad
    
    • 1
    • 2

    第三方依赖jar包在/lib目录下 类加载器都通过TomcatEmbeddedWebappClassLoader的父LaunchedURLClassLoader加载器加载的,因此打包后,会出现CompletableFuture 有些类无法加载(因为默认的CommonJoinPool设置了类加载器为AppClassLoader),委派给父类 父类也无法加载,就会导致错误发生(比如:我遇到的RPC返回的类型本来是自己自定义的类,导致返回hashmap,然后强转类型失败)

    4.解决方案

    a.重新 设置自己类加载器

      ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
     CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
                try {
                     Thread.currentThread().setContextClassLoader(contextClassLoader );
                    Thread.sleep(10_000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "";
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    b.使用自定义线程池

     CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
                try {
                     Thread.currentThread().setContextClassLoader(contextClassLoader );
                    Thread.sleep(10_000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "";
            },Executors.newFixedThreadPool(3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5.总结

    1. 通过springboot的maven插件打包后的jar包,项目的依赖jar包的类是通过LaunchedURLClassLoader加载

    2. CompletableFuture的默认线程池为ForkJoinPool,而它默认的线程工厂是设置了类加载器为AppClassloader

    3. idea下点击运行并没有打成jar包运行,而是通过编译java文件,再使用java命令加上classpath参数运行,项目的依赖jar包都是设置变成了classpath变量下的路径找到,从而可以通过AppClassLoader类加载器加载,导致了idea运行的时候看不出来任何问题,打成springboot 的jar包后运行报错

  • 相关阅读:
    容器编排学习(五)卷的概述与存储卷管理
    VirtualBox Ubuntu系统硬盘扩容
    python自动化之——获取钉钉群所有人的昵称
    webpack面试题
    C语言学生信息管理系统
    CAD图清晰打印设置
    excel 十万级数据秒级导出
    小程序容器如何确保小程序安全?
    逻辑漏洞挖掘之XSS漏洞原理分析及实战演练 | 京东物流技术团队
    代码行统计工具---cloc(Count Lines of Code)
  • 原文地址:https://blog.csdn.net/qq_40836488/article/details/126367111