关于后端:升级了Springboot版本后项目启动不了了

问题背景

我的项目上应用的springboot版本是2.1.1.RELEASE,当初因为要接入elasticsearch7.x版本,参考官网文档要求,须要将springboot版本升级到2.5.14

本认为是改一下版本号的事,然而降级之后发现服务启动报错了😱。


问题起因

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."); 
    }
    
    ...
}

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

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

咱们先来打断点看看,这里理论应用到的是哪个类的实例,先将springboot版本改回到2.1.1.RELEASE看看,能够看到应用的是LocalDataSourceJobStore,而当咱们应用springboot2.5.14版本时,这里应用的是JobStoreTX是实例化对象。

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

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

接下来咱们看一下我的项目中与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,不关怀之前有没有设置过该值。

org.springframework.scheduling.quartz.SchedulerFactoryBean#initSchedulerFactory” title=”org.springframework.scheduling.quartz.SchedulerFactoryBean#initSchedulerFactory“>


解决方案

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

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

再次启动服务,功败垂成✌️。

本文由mdnice多平台公布

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理