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包后运行报错