一、问题描述
首先看一下程序上的版本依赖
SpringBoot 2.1.4
Nacos 2.1.4
shedlock2.2.1
背景:
之前项目是好好的,用的是Eurake + config 现在开始进行改造打算用上Nacos。
二、配置相关
ShedlockConfig
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
@Bean
public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder
.withLockProvider(lockProvider)
// 线程池10个线程
.withPoolSize(10)
.withDefaultLockAtMostFor(Duration.ofMinutes(10))
.build();
}
Nacos 就是加Jar包,写yml文件。略略略
三、报错信息
这里只找了一部分比较关键的
2022-11-09:11:07:31.631 [main] ERROR o.s.b.SpringApplication - [reportFailure,858] - Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nacosWatch' defined in class path resource [com/alibaba/cloud/nacos/discovery/NacosDiscoveryClientConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.alibaba.cloud.nacos.discovery.NacosWatch]: Factory method 'nacosWatch' threw exception; nested exception is java.lang.ClassCastException: com.sun.proxy.$Proxy249 cannot be cast to org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.alibaba.cloud.nacos.discovery.NacosWatch]: Factory method 'nacosWatch' threw exception; nested exception is java.lang.ClassCastException: com.sun.proxy.$Proxy249 cannot be cast to org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622)
... 19 common frames omitted
Caused by: java.lang.ClassCastException: com.sun.proxy.$Proxy249 cannot be cast to org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
四、问题解析
1、首先看到这个问题,发现是Nacos 中的com.alibaba.cloud.nacos.discovery.NacosWatch 这个类报的错误。
2、再分析一下报错原因
Factory method ‘nacosWatch’ threw exception; nested exception is java.lang.ClassCastException: com.sun.proxy.$Proxy249 cannot be cast to org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
简单来说就是在类型转换的时候转换失败了。我理解就是获取到了不是他自己的Bean 对象。
3、看一下NacosWatch 类。
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = true)
public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties,
ObjectProvider<ThreadPoolTaskScheduler> taskExecutorObjectProvider) {
return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties,
taskExecutorObjectProvider);
}
这里大家就能看到问题了,首先这个是一个配置类,在Spring 装配这个类的时候会把这个参数进行注入。回想一下Spring Bean 的装配流程。加载XML 扫描Class,变成一个一个的BeanDefinition,实例化,设置属性,,,,扯得有点远了。
总之这个方法会在IoC 容器中找一找看看有没有这几个类型的Bean 对象。(这个也是导致错误的原因)
4、再看一下new NacosWatch 方法都做了什么处理。
// 上面的构造方法
public NacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties properties,
ObjectProvider<ThreadPoolTaskScheduler> taskScheduler) {
this.nacosServiceManager = nacosServiceManager;
this.properties = properties;
this.taskScheduler = taskScheduler.stream().findAny()
.orElseGet(NacosWatch::getTaskScheduler);
}
// 这个就是在容器获取不到对象的时候会调用这个方法创建一个对象
private static ThreadPoolTaskScheduler getTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setBeanName("Nacos-Watch-Task-Scheduler");
taskScheduler.initialize();
return taskScheduler;
}
5、粘一个错误的Debug 信息。
叭了叭,到这里判断的时候value 有值了,这个值是哪污染的呢?这个问题纠结了好久,突然想到是不是定时任务那块创建的。
6、排查问题就是猜猜猜,试试试。观察一下分布式定时任务的代码。
//再观察一下 这个配置类
@Bean
public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder
.withLockProvider(lockProvider)
// 线程池10个线程
.withPoolSize(10)
.withDefaultLockAtMostFor(Duration.ofMinutes(10))
.build();
}
进入到 withLockProvider 这个方法里面。
发现这里会创建一个taskSchedule 类型的对象。
巴拉巴拉再对比一下Nacos 的获取到的类信息。
是不是感觉似曾相识。
总结一下问题原因,是因为NacosWatch 这个在创建的时候会判断是否已经有这个有的话就不会再去,创建。正好因为分布式定时任务,创建了一个,Nacos 获取到这个类。结果类型转换错误了。。。
五、解决办法
一、关闭Nacos 的watch
spring:
cloud:
nacos:
discovery:
watch:
enabled: false
二、修改定时配置的方式
@Bean(destroyMethod = "shutdown")
public ScheduledExecutorService scheduledExecutorService(){
return Executors.newScheduledThreadPool(10);
}
// @Bean
// public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
// return ScheduledLockConfigurationBuilder
// .withLockProvider(lockProvider)
// // 线程池10个线程
// .withPoolSize(10)
// .withDefaultLockAtMostFor(Duration.ofMinutes(10))
// .build();
// }
代替原有的配置。
目前只是初步解决方案。
结束
文章最后,首先要感谢一下我的老师,景天老师。帮我排查了指导问题。
文章还有好多的知识上的漏洞,作者也在慢慢的完善,进步。
借老师一句话,用屁股都能想明白,这就是依赖冲突导致的。