1

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

 2 years ago
source link: https://www.cnblogs.com/mindforward/p/16486148.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

photo-1501769630498-260b0ac4458d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxNDY1OTN8MHwxfHNlYXJjaHwxMnx8YW1hemluZ3xlbnwwfDB8fHwxNjU3OTQwNDA1&ixlib=rb-1.2.1&q=100&w=1000

问题背景#

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

springboot与elasticsearch版本对照表

springboot版本表

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

74fa51e9-afb3-4128-bdb4-0af2cea266d2.png

问题原因#

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

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

5dd989a5-3993-480a-9d58-2bd3ec37cf01.png

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

276e921d-db18-48fd-a740-0c5216852e69.png

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

35b6bdc6-d8fb-4946-862c-f3b0a33e12b8.png

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

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

6c5234fd-0acd-4bdb-a9c1-e3e90314449a.png

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

1e51d8db-8e6d-4bcc-8fb4-7c749a3260c4.png

解决方案#

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK