前言
前段时间,公司项目升级了JDK21,由于SpringBoot2的一些致命漏洞,其实领导很早就下达了任务,年底是最后的期限。至于升级过程中大部分都依靠AI Agent完成的,再加上我们项目组是最晚升级的那一批,所以升级过程还算比较顺利,但还是遇到了一些坑,这里记录下我在升级过程中踩到的坑。
CompletableFuture异步任务报错
报错
在使用CompletableFuture.runAsync() 方法异步调用其它微服务时,出现了报错:
org.springframework.http.client.ClientHttpRequest referenced from a method is not visible from class loader: 'app'
原因
核心原因:
当直接使用 CompletableFuture.runAsync(() -> { ... }) 而不指定线程池时,默认使用的是 JDK 的 ForkJoinPool.commonPool()。
问题在于:
ForkJoinPool 中的线程通常是由 System ClassLoader(系统类加载器)加载的,但在 JDK 9 引入模块化系统(JPMS)之后,一直到 JDK 21,JVM 对 ForkJoinPool 做了严格的限制。
当异步线程试图加载 ClientHttpRequest 等 Spring 类时,由于类加载器隔离(Visibility),ForkJoinPool 的线程看不到 LaunchedURLClassLoader 中的类(位于 BOOT-INF/lib 下),从而报出 not visible from class loader 错误。
解决方案
网上大多数解决方案基本有两种,1. 自定义 ForkJoinPool 工厂 2. 自定义线程池
其中自定义 ForkJoinPool 工厂有点麻烦,博主并不推荐,自定义线程池倒是一个比较稳的方案。不过除了这两种方案,经过博主实践,其实还有两种更简单的方案,那就是直接使用虚拟线程池或者使用@Async注解替换CompletableFuture
自定义线程池
@EnableAsync
public class AsyncConfig {
@Bean("ahzooAsyncExecutor")
public Executor ahzooAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Async-Ahzoo-Service-");
// 这一步非常关键,它会初始化线程并继承父线程的上下文
executor.initialize();
return executor;
}
}
@Autowired
@Qualifier("ahzooAsyncExecutor")
private Executor executor;
public void callMicroservice() {
CompletableFuture.runAsync(() -> {
// xxxxxxx
}, executor); // <--- 关键点:传入 executor
}
虚拟线程
直接在配置文件中全局开启虚拟线程:
spring:
threads:
virtual:
enabled: true
自定义ForkJoinPool 工厂
关于自定义自定义 ForkJoinPool 工厂的方法,可以在stackoverflow的这篇文章找到相关回答:CompletableFuture / ForkJoinPool Set Class Loader
这里还没有评论哦
快来发一条评论抢占前排吧