问题背景
我的项目上应用的 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 多平台公布