升级了Springboot版本后项目启动不了了

升级了Springboot版本后项目启动不了了

问题背景

项目上使用的springboot版本是2.1.1.RELEASE,现在因为要接入elasticsearch7.x版本,参考官方文档要求,需要将springboot版本升级到2.5.14

升级了Springboot版本后项目启动不了了

升级了Springboot版本后项目启动不了了

本以为是改一下版本号的事,但是升级之后发现服务启动报错了😱。
升级了Springboot版本后项目启动不了了


问题原因

Caused by: org.quartz.SchedulerConfigException: DataSource name not set.从错误信息中可以看出,报错与quartz有关,我们先来顺着异常栈看一下,可以看到是JobStoreSupport.initialize()方法中抛出的错误:

public void initialize(ClassLoadHelper loadHelper,         SchedulerSignaler signaler) throws SchedulerConfigException {      if (dsName == null) {          throw new SchedulerConfigException("DataSource name not set.");      }          ... } 

向上溯源可以发现,其初始调用方是这里:

升级了Springboot版本后项目启动不了了

那么这个js对象是什么呢?它是一个JobStore实例,而JobStore其实是一个接口,它有多个实现类:

升级了Springboot版本后项目启动不了了

我们先来打断点看看,这里实际使用到的是哪个类的实例,先将springboot版本改回到2.1.1.RELEASE看看,可以看到使用的是LocalDataSourceJobStore,而当我们使用springboot2.5.14版本时,这里使用的是JobStoreTX是实例化对象。

升级了Springboot版本后项目启动不了了

那么,是什么原因导致两个版本使用了不同的JobStore实现类呢?先来看看js对象是如何初始化的:首先获取org.quartz.jobStore.class属性值,然后通过反射实例化js对象。

显然,在不同版本下,这里获取到的org.quartz.jobStore.class属性值不一致导致了创建了不同的JobStore实现类。

升级了Springboot版本后项目启动不了了

接下来我们看一下项目中与quartz相关的配置,如下代码所示,是一个SchedulerFactoryBean的初始化操作 ,其中设置org.quartz.jobStore.class属性值为org.quartz.impl.jdbcjobstore.JobStoreTX,但是从上面分析中我们知道,在真正创建JobStore实现类时,这个属性值已经发生了变化,由此说明这个值在后期被更改过。

@Configuration public class ScheduleConfig {     @Bean     public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)     {         SchedulerFactoryBean factory = new SchedulerFactoryBean();         factory.setDataSource(dataSource);          Properties prop = new Properties();         prop.put("org.quartz.scheduler.instanceName", "MyScheduler");         prop.put("org.quartz.scheduler.instanceId", "AUTO");         prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");         prop.put("org.quartz.threadPool.threadCount", "20");         prop.put("org.quartz.threadPool.threadPriority", "5");                  // 这里设置org.quartz.jobStore.class属性值为org.quartz.impl.jdbcjobstore.JobStoreTX         prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");         prop.put("org.quartz.jobStore.isClustered", "true");         prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");         prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");         prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");         prop.put("org.quartz.jobStore.misfireThreshold", "12000");         prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");         factory.setQuartzProperties(prop);          factory.setSchedulerName("MyScheduler");          factory.setStartupDelay(1);         factory.setApplicationContextSchedulerContextKey("applicationContext");         factory.setOverwriteExistingJobs(true);         factory.setAutoStartup(true);          return factory;     } } 

而我们从异常栈中可以发现,SchedulerFactoryBean在完成初始化操作之后,执行了afterPropertiesSet()方法,先来看一下这个方法中做了哪些事情:

public void afterPropertiesSet() throws Exception {   if (this.dataSource == null && this.nonTransactionalDataSource != null) {     this.dataSource = this.nonTransactionalDataSource;   }    if (this.applicationContext != null && this.resourceLoader == null) {     this.resourceLoader = this.applicationContext;   }    // Initialize the Scheduler instance...   // 先来看看this.prepareSchedulerFactory()方法中做了什么   this.scheduler = this.prepareScheduler(this.prepareSchedulerFactory());    try {     this.registerListeners();     this.registerJobsAndTriggers();   } catch (Exception var4) {     try {       this.scheduler.shutdown(true);     } catch (Exception var3) {       this.logger.debug("Scheduler shutdown exception after registration failure", var3);     }      throw var4;   } }   /**  * Create a SchedulerFactory if necessary and apply locally defined Quartz properties to it.  * @return the initialized SchedulerFactory  */ private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {   SchedulerFactory schedulerFactory = this.schedulerFactory;   if (schedulerFactory == null) {     // Create local SchedulerFactory instance (typically a StdSchedulerFactory)     schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);     if (schedulerFactory instanceof StdSchedulerFactory) {       // 重点来了,这里有一个SchedulerFactory初始化方法       initSchedulerFactory((StdSchedulerFactory) schedulerFactory);     }     else if (this.configLocation != null || this.quartzProperties != null ||         this.taskExecutor != null || this.dataSource != null) {       throw new IllegalArgumentException(           "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);     }     // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties)   }   // Otherwise, assume that externally provided factory has been initialized with appropriate settings   return schedulerFactory; }  

接下来我们进入initSchedulerFactory()方法内部看看具体都有哪些逻辑,这是一个SchedulerFactory初始化方法,它会应用Quartz的一些本地配置属性用于SchedulerFactory初始化:

/**  * Initialize the given SchedulerFactory, applying locally defined Quartz properties to it.  * @param schedulerFactory the SchedulerFactory to initialize  */ private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException {   Properties mergedProps = new Properties();   if (this.resourceLoader != null) {     mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS,         ResourceLoaderClassLoadHelper.class.getName());   }    if (this.taskExecutor != null) {     mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS,         LocalTaskExecutorThreadPool.class.getName());   }   else {     // Set necessary default properties here, as Quartz will not apply     // its default configuration when explicitly given properties.     mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());     mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT));   }    if (this.configLocation != null) {     if (logger.isDebugEnabled()) {       logger.debug("Loading Quartz config from [" + this.configLocation + "]");     }     PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);   }    CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);   if (this.dataSource != null) {     // 重点来了,如果未设置"org.quartz.jobStore.class"属性,就将其设置为"org.springframework.scheduling.quartz.LocalDataSourceJobStore"     mergedProps.putIfAbsent(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());   }    // Determine scheduler name across local settings and Quartz properties...   if (this.schedulerName != null) {     mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName);   }   else {     String nameProp = mergedProps.getProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME);     if (nameProp != null) {       this.schedulerName = nameProp;     }     else if (this.beanName != null) {       mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.beanName);       this.schedulerName = this.beanName;     }   }    schedulerFactory.initialize(mergedProps); } 

也就是说,当我们配置了org.quartz.jobStore.class属性时,在springboot2.5.14版本中会以我们代码中配置的为准,也就是org.quartz.impl.jdbcjobstore.JobStoreTX

再来看一下springboot2.1.1.RELEASE版本中此处的逻辑,它会直接把org.quartz.jobStore.class属性值设置为org.springframework.scheduling.quartz.LocalDataSourceJobStore,不关心之前有没有设置过该值。

升级了Springboot版本后项目启动不了了


解决方案

明白了问题的症结所在,解决起来就相当容易了,两种方式:

  • 去掉ScheduleConfig配置类中SchedulerFactoryBean对象的org.quartz.jobStore.class属性配置,交由SchedulerFactoryBean#initSchedulerFactor去设置。
  • 直接将ScheduleConfig配置类中SchedulerFactoryBean对象的org.quartz.jobStore.class属性值设置为LocalDataSourceJobStore.class.getName()

再次启动服务,大功告成✌️。

发表评论

相关文章