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


    问题背景#

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

    springboot与elasticsearch版本对照表

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

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

    那么这个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,不关心之前有没有设置过该值。


    解决方案#

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

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

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

  • 相关阅读:
    C++学习之旅 第二章 printf与cout
    JavaScript运算符
    实现一个会动的鸿蒙 LOGO
    推特API(Twitter API)V2 查询用户信息
    Camtasia Studio2023喀秋莎免费实用的屏幕录像工具
    AJAX初
    CUDA By Example(三)——CUDA C并行编程
    Linux 文件系统(一) --- ext4文件系统简介
    公众号如何让更多人看到?这三种方法超有效!
    【操作系统】BIOS开机自检
  • 原文地址:https://www.cnblogs.com/mindforward/p/16486148.html