问题背景

项目上使用的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,不关心之前有没有设置过该值。


解决方案

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

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

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

升级了Springboot版本后项目启动不了了的更多相关文章

  1. 如何避免升级 Linux 实例内核后无法启动

    如何避免升级 Linux 实例内核后无法启动_系统配置_操作运维 Linux_常见问题_云服务器 ECS-阿里云 https://help.aliyun.com/knowledge_detail/59 ...

  2. 玩转SpringBoot 2 之项目启动篇

    SpringBoot 启动方式有那些? SpringBoot 有4种方式进行启动,具体方式如下: IDEA方式启动 Eclipse 方式启动 Maven 启动方式 通过SpringBoot 程序 ja ...

  3. struts升级到最高版本后遇到的问题。关于actionmessage传递问题。

    Struts2升级到最新版本遇到的一些问题 首先是更换对应的jar,如asm.common.ongl.struts等等.更换后发现系统启动不了,按照网上的介绍,先后又更新了slf4j-log4j12- ...

  4. 升级了git版本后git clone报ssl错误的解决方法

    由于升级了git版本,git clone 的时候报了如下的错误 fatal: unable to access 'https://github.com/open-falcon/falcon-plus. ...

  5. DEDE升级5.7版本后生成页面空白_解…

    今天将DEDECMS V5.6升级到DEDECMS V5.7并升级5.7 SP1后,发现生成首页.栏目.内容页均为空白,没有任何反应,今天发布一个解决方法. 发现每个模板中调用过 Html2Text ...

  6. fastDfs V5.02 升级到 V5.08版本后,启动报错:symbol lookup error: /usr/bin/fdfs_trackerd: undefined symbol: g_current_time

    /libfastcommon-1.0.36 # ./make.sh cc -Wall -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -g -O3 -c -o hash.o ...

  7. 【Finchley】【升级变更】Spring Cloud 升级到Finchley版本后需要注意的地方

    Spring Boot 2.x 已经发布了很久,现在 Spring Cloud 也发布了 基于 Spring Boot 2.x 的 Finchley 版本,现在一起为项目做一次整体框架升级. 升级前 ...

  8. springboot 初始化 web 项目 启动报错。。。一直解决不了

    1. 一个简单的SpringBoot项目,启动时报错信息: ERROR 18688 --- [cat-startStop-1] org.apache.catalina.core.ContainerBa ...

  9. Ionic CLI升级到3版本后2版本工程运行出错.md

    1. 问题描述: 最近将Ionic的CLI升级到了最新的版本3.2,升级后在原来Ionic2版本的CLI中创建的工程,通过ionic serve运行报错,错误类似如下: C:\Users\Admin\ ...

随机推荐

  1. 【Azure Developer】使用 adal4j(Azure Active Directory authentication library for Java)如何来获取Token呢

    问题描述 使用中国区的Azure,在获取Token时候,参考了 adal4j的代码,在官方文档中,发现了如下的片段代码: ExecutorService service = Executors.new ...

  2. [没接触过kubevirt?]15分钟快速入门kubevirt

    @ 目录 本文介绍 前言 环境准备 详细版 搭建步骤 安装KubeVirt 安装virtctl客户端工具 创建VirtualMachine 启动VirtualMachineInstance 启动和停止 ...

  3. 修复Arch Linux和Manjaro Linux无法显示emoji的问题

    安装好Arch Linux或Manjaro Linux系统后默认没办法正常显示emoji,通常会变成方框或者带有unicode码的方块: 这是因为缺失字体以及相关的字体配置导致的. 当然也有一小部分应 ...

  4. muduo源码分析之Buffer

    这一次我们来分析下muduo中Buffer的作用,我们知道,当我们客户端向服务器发送数据时候,服务器就会读取我们发送的数据,然后进行一系列处理,然后再发送到其他地方,在这里我们想象一下最简单的Echo ...

  5. k8s client-go源码分析 informer源码分析(3)-Reflector源码分析

    k8s client-go源码分析 informer源码分析(3)-Reflector源码分析 1.Reflector概述 Reflector从kube-apiserver中list&watc ...

  6. VMWare中CentOS安装VM-Tools

    查看CD-ROM驱动器的设备信息 可以通过下面几个命令来查看 dmesg命令 dmesg | egrep -i --color 'cdrom|dvd|cd/rw|writer' /proc/sys/d ...

  7. 使用虚拟机在3台centos7系统安装docker和k8s集群

    一.安装docker 环境:准备3台centos7系统,都安装上docker环境,具体安装步骤和流程如下 参考: https://docs.docker.com/install/linux/docke ...

  8. SpringBoot进阶教程(七十四)整合ELK

    在上一篇文章<SpringBoot进阶教程(七十三)整合elasticsearch >,已经详细介绍了关于elasticsearch的安装与使用,现在主要来看看关于ELK的定义.安装及使用 ...

  9. 基于C++11的数据库连接池实现

    0.注意 该篇文章为了让大家尽快看到效果,代码放置比较靠前,看代码前务必看下第4部分的基础知识. 1.数据库连接池 1.1 是什么? 数据库连接池负责分配.管理和释放数据库连接,属于池化机制的一种,类 ...

  10. 测试open

    // 此处,返回的 undefined 是 JS 中的一个值 return undefined } // 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同 const add ...