在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

对于CompletableFuture来说,如果用户使用的时候没有手动设置线程池,那么CompletableFuture默认会使用ForkJoinPool

ForJoinPool初始化的时候有一个默认的线程工厂

在默认的线程工厂中设置了创建线程的时候使用Application ClassLoader(应用程序类加载器) 可以加载classpath下的类


所以在idea中运行 CompletableFuture 加载类通过双亲委派机制都是没有问题的
通过springboot的maven插件打包后,类加载器如下:
TomcatEmbeddedWebappClassLoader-->LaunchedURLClassLoader
-->AppClassLoader-->ExtClassLoader-->BootstrapClassLoad
第三方依赖jar包在/lib目录下 类加载器都通过TomcatEmbeddedWebappClassLoader的父LaunchedURLClassLoader加载器加载的,因此打包后,会出现CompletableFuture 有些类无法加载(因为默认的CommonJoinPool设置了类加载器为AppClassLoader),委派给父类 父类也无法加载,就会导致错误发生(比如:我遇到的RPC返回的类型本来是自己自定义的类,导致返回hashmap,然后强转类型失败)
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 "";
});
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.currentThread().setContextClassLoader(contextClassLoader );
Thread.sleep(10_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
},Executors.newFixedThreadPool(3);
1. 通过springboot的maven插件打包后的jar包,项目的依赖jar包的类是通过LaunchedURLClassLoader加载
2. CompletableFuture的默认线程池为ForkJoinPool,而它默认的线程工厂是设置了类加载器为AppClassloader的
3. idea下点击运行并没有打成jar包运行,而是通过编译java文件,再使用java命令加上classpath参数运行,项目的依赖jar包都是设置变成了classpath变量下的路径找到,从而可以通过AppClassLoader类加载器加载,导致了idea运行的时候看不出来任何问题,打成springboot 的jar包后运行报错