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

发布于:2023-01-15 ⋅ 阅读:(318) ⋅ 点赞:(0)

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;.... .....    类名

在这里插入图片描述
它的所有依赖类加载器都是使用的是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

第三方依赖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 "";
        });

b.使用自定义线程池

 CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            try {
                 Thread.currentThread().setContextClassLoader(contextClassLoader );
                Thread.sleep(10_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        },Executors.newFixedThreadPool(3);

5.总结

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

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

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