Spring Boot多租户应用java -jar启动时工作线程加载上下文报AutoProxyRegistrar类不存在
问题根因
这个问题本质是Spring Boot FatJar的类加载器和工作线程上下文类加载器不匹配导致的,不是Spring AOP的bug,也不是配置遗漏:
- Spring Boot打包的FatJar启动时,主线程的上下文类加载器是Spring Boot自定义的
LaunchedURLClassLoader,负责加载FatJar内部嵌套的依赖Jar(比如spring-context、spring-aop等jar都在BOOT-INF/lib下) - 你创建的线程池(包括并行流默认用的
ForkJoinPool.commonPool())的工作线程默认继承的是系统类加载器AppClassLoader,这个类加载器无法识别FatJar内部的嵌套Jar路径,所以找不到AutoProxyRegistrar.class这类Spring核心类 - 开发阶段用
spring-boot:run启动时,所有依赖直接暴露在系统类路径下,系统类加载器可以直接加载所有类,所以不会触发这个问题 - AOP的
internalAutoProxyCreator未注册是上层现象:因为AOP自动配置依赖的类加载失败,导致相关配置类没有生效,自然不会注册这个Bean
解决方案
只需要给所有加载租户上下文的工作线程,手动设置和主线程一致的上下文类加载器即可:
方案1:自定义线程池时显式设置类加载器
在主线程中先拿到当前的上下文类加载器,构造线程池时通过线程工厂给所有工作线程设置该类加载器:
// 主线程中提前获取正确的类加载器 ClassLoader mainContextClassLoader = Thread.currentThread().getContextClassLoader(); // 构造自定义线程池 ExecutorService tenantContextExecutor = Executors.newFixedThreadPool(4, runnable -> { Thread workerThread = new Thread(runnable); // 给工作线程设置正确的类加载器 workerThread.setContextClassLoader(mainContextClassLoader); return workerThread; });
方案2:使用并行流时使用自定义ForkJoinPool
并行流默认用的公共ForkJoinPool的线程不会继承你的主线程类加载器,需要自行构造专用的ForkJoinPool:
ClassLoader mainContextClassLoader = Thread.currentThread().getContextClassLoader(); // 构造自定义ForkJoinPool ForkJoinPool tenantForkJoinPool = new ForkJoinPool(4, pool -> { ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); worker.setContextClassLoader(mainContextClassLoader); return worker; }, null, false); // 把并行流任务提交到自定义池执行 tenantForkJoinPool.submit(() -> { tenantList.parallelStream().forEach(tenant -> loadTenantContext(tenant)); }).get();
临时验证方案
你可以在加载租户上下文的代码开头加一行日志,确认工作线程的类加载器是否正确:
log.info("当前线程类加载器:{}", Thread.currentThread().getContextClassLoader().getClass().getName());
正常情况下java -jar启动时,主线程和工作线程输出的都应该是org.springframework.boot.loader.LaunchedURLClassLoader。
升级说明
即使你后续升级到Spring Boot 2.6+版本,FatJar的类加载机制也没有变化,以上修改仍然是必须的,不会和你等待的Spring Data JDBC修复冲突。
内容的提问来源于stack exchange,提问作者gmich
相关产品推荐
相关产品推荐

