一、Error日志&原因
最近在搞Springboot整合Quartz2.3.0的时候遇到了一个诡异的问题,工程启动不起来。
错误log:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schedulerFactoryBean' defined in class path resource [com/nana/matrix/timer/quartz/QuartzConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1803)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1287)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:874)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:778)
... 47 common frames omitted
Caused by: java.lang.IllegalStateException: Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!
at org.springframework.scheduling.quartz.SchedulerFactoryBean.createScheduler(SchedulerFactoryBean.java:679)
at org.springframework.scheduling.quartz.SchedulerFactoryBean.prepareScheduler(SchedulerFactoryBean.java:614)
at org.springframework.scheduling.quartz.SchedulerFactoryBean.afterPropertiesSet(SchedulerFactoryBean.java:502)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1862)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1799)
... 58 common frames omitted
排查了很久,以为是配置文属性配置的不对,或者是工程内另外一个类QuartzDemo代码影响的。结果,发现都不是。没办法,就断点debug,查看quartz源码,最终发现了问题出现的根本原因。
根本原因是:
工程里面我的quartz的配置文件名为quartz.properties,跟Quartz默认的配置文件名quartz.properties相同。导致工程启动的时候,我本地的quartz配置文件覆盖了系统的配置文件。工程启动时,首先进入QuartzInitializerListener.java,执行了contextInitialized()方法,并实例化了一个Scheduler。随后,进入QuartzConfig.java中执行了 schedulerFactoryBean()方法,并在在执行
propertiesFactoryBean.getObject();后进入SchedulerFactoryBean.java类中,执行了
afterPropertiesSet()
prepareSchedulerFactory()
createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName)
从而再一次实例化了一个相同的Scheduler,导致Scheduler实例化了两次,所以报Error:
Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!
二、我的QuartzConfig.java类
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
/**
* Quartz配置类
*
* @author xingnana
* @create 2020-01-10
*/
@Configuration
public class QuartzConfig {
// 配置文件路径
// 注:配置文件名不能与默认的文件quartz.properties相同,否则报:Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!
private static final String QUARTZ_CONFIG = "/quartz.properties";
@Autowired
DataSource dataSource;
/**
* 调度器
* @param jobFactory
* @return
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory)throws IOException{
SchedulerFactoryBean schedulerFactory=new SchedulerFactoryBean();
schedulerFactory.setDataSource(dataSource);
// 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
schedulerFactory.setOverwriteExistingJobs(true);
schedulerFactory.setAutoStartup(true);
schedulerFactory.setStartupDelay(1);
schedulerFactory.setQuartzProperties(quartzProperties());
schedulerFactory.setJobFactory(jobFactory);
return schedulerFactory;
}
/**
* 从quartz.properties文件中读取Quartz配置属性
* @return
* @throws IOException
*/
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource(QUARTZ_CONFIG));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/**
* quartz初始化监听器
* 这个监听器可以监听到工程的启动,在工程停止再启动时可以让已有的定时任务继续进行。
* 有这个会导致报错:Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!
* @return
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/**
*
*通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="scheduler")
public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) throws SchedulerException {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
return scheduler;
}
}
三、Debug记录
首先看一下QuartzInitializerListener源码,该类在工程启动是执行。
QuartzInitializerListener.java源码:
public void contextInitialized(ServletContextEvent sce) {
this.log.info("Quartz Initializer Servlet loaded, initializing Scheduler...");
ServletContext servletContext = sce.getServletContext();
try {
String configFile = servletContext.getInitParameter("quartz:config-file");
if (configFile == null) {
configFile = servletContext.getInitParameter("config-file");
}
String shutdownPref = servletContext.getInitParameter("quartz:shutdown-on-unload");
if (shutdownPref == null) {
shutdownPref = servletContext.getInitParameter("shutdown-on-unload");
}
if (shutdownPref != null) {
this.performShutdown = Boolean.valueOf(shutdownPref);
}
String shutdownWaitPref = servletContext.getInitParameter("quartz:wait-on-shutdown");
if (shutdownPref != null) {
this.waitOnShutdown = Boolean.valueOf(shutdownWaitPref);
}
// 根据配置文件,实例化Scheduler工厂
StdSchedulerFactory factory = this.getSchedulerFactory(configFile);
// 获取scheduler
this.scheduler = factory.getScheduler();
String startOnLoad = servletContext.getInitParameter("quartz:start-on-load");
if (startOnLoad == null) {
startOnLoad = servletContext.getInitParameter("start-scheduler-on-load");
}
int startDelay = 0;
String startDelayS = servletContext.getInitParameter("quartz:start-delay-seconds");
if (startDelayS == null) {
startDelayS = servletContext.getInitParameter("start-delay-seconds");
}
try {
if (startDelayS != null && startDelayS.trim().length() > 0) {
startDelay = Integer.parseInt(startDelayS);
}
} catch (Exception var12) {
this.log.error("Cannot parse value of 'start-delay-seconds' to an integer: " + startDelayS + ", defaulting to 5 seconds.");
startDelay = 5;
}
if (startOnLoad != null && !Boolean.valueOf(startOnLoad)) {
this.log.info("Scheduler has not been started. Use scheduler.start()");
} else if (startDelay <= 0) {
this.scheduler.start();
this.log.info("Scheduler has been started...");
} else {
this.scheduler.startDelayed(startDelay);
this.log.info("Scheduler will start in " + startDelay + " seconds.");
}
String factoryKey = servletContext.getInitParameter("quartz:servlet-context-factory-key");
if (factoryKey == null) {
factoryKey = servletContext.getInitParameter("servlet-context-factory-key");
}
if (factoryKey == null) {
factoryKey = "org.quartz.impl.StdSchedulerFactory.KEY";
}
this.log.info("Storing the Quartz Scheduler Factory in the servlet context at key: " + factoryKey);
servletContext.setAttribute(factoryKey, factory);
String servletCtxtKey = servletContext.getInitParameter("quartz:scheduler-context-servlet-context-key");
if (servletCtxtKey == null) {
servletCtxtKey = servletContext.getInitParameter("scheduler-context-servlet-context-key");
}
if (servletCtxtKey != null) {
this.log.info("Storing the ServletContext in the scheduler context at key: " + servletCtxtKey);
this.scheduler.getContext().put(servletCtxtKey, servletContext);
}
} catch (Exception var13) {
this.log.error("Quartz Scheduler failed to initialize: " + var13.toString());
var13.printStackTrace();
}
}
断点截图:
StdSchedulerFactory.java 的 getScheduler() 方法源码:
public Scheduler getScheduler() throws SchedulerException {
if (this.cfg == null) {
// 初始化 StdSchedulerFactory
this.initialize();
}
SchedulerRepository schedRep = SchedulerRepository.getInstance();
Scheduler sched = schedRep.lookup(this.getSchedulerName());
if (sched != null) {
if (!sched.isShutdown()) {
return sched;
}
schedRep.remove(this.getSchedulerName());
}
// 调用 StdSchedulerFactory的方法,根据配置文件初始化Scheduler
sched = this.instantiate();
return sched;
}
断点截图:
StdSchedulerFactory类有三个初始化方法,最开始调用的是无参的initialize()
StdSchedulerFactory.java 的initialize() 方法源码,它接在上面的getScheduler() 中被调用:
public void initialize() throws SchedulerException {
if (this.cfg == null) {
if (this.initException != null) {
throw this.initException;
} else {
String requestedFile = System.getProperty("org.quartz.properties");
String propFileName = requestedFile != null ? requestedFile : "quartz.properties";
File propFile = new File(propFileName);
Properties props = new Properties();
Object in = null;
try {
if (propFile.exists()) {
try {
if (requestedFile != null) {
this.propSrc = "specified file: '" + requestedFile + "'";
} else {
this.propSrc = "default file in current working dir: 'quartz.properties'";
}
in = new BufferedInputStream(new FileInputStream(propFileName));
// 加载本地的配置文件 quartz.properties
props.load((InputStream)in);
} catch (IOException var19) {
this.initException = new SchedulerException("Properties file: '" + propFileName + "' could not be read.", var19);
throw this.initException;
}
} else if (requestedFile != null) {
in = Thread.currentThread().getContextClassLoader().getResourceAsStream(requestedFile);
if (in == null) {
this.initException = new SchedulerException("Properties file: '" + requestedFile + "' could not be found.");
throw this.initException;
}
this.propSrc = "specified file: '" + requestedFile + "' in the class resource path.";
in = new BufferedInputStream((InputStream)in);
try {
props.load((InputStream)in);
} catch (IOException var18) {
this.initException = new SchedulerException("Properties file: '" + requestedFile + "' could not be read.", var18);
throw this.initException;
}
} else {
this.propSrc = "default resource file in Quartz package: 'quartz.properties'";
ClassLoader cl = this.getClass().getClassLoader();
if (cl == null) {
cl = this.findClassloader();
}
if (cl == null) {
throw new SchedulerConfigException("Unable to find a class loader on the current thread or class.");
}
in = cl.getResourceAsStream("quartz.properties");
if (in == null) {
in = cl.getResourceAsStream("/quartz.properties");
}
if (in == null) {
in = cl.getResourceAsStream("org/quartz/quartz.properties");
}
if (in == null) {
this.initException = new SchedulerException("Default quartz.properties not found in class path");
throw this.initException;
}
try {
props.load((InputStream)in);
} catch (IOException var17) {
this.initException = new SchedulerException("Resource properties file: 'org/quartz/quartz.properties' could not be read from the classpath.", var17);
throw this.initException;
}
}
} finally {
if (in != null) {
try {
((InputStream)in).close();
} catch (IOException var16) {
}
}
}
// 再次根据配置文件quartz.properties初始化StdSchedulerFactory
this.initialize(this.overrideWithSysProps(props));
}
}
}
断点截图,从该截图可以看到本地的quartz配置文件,覆盖了系统的quartz配置文件。
StdSchedulerFactory.java 的 getScheduler()继续执行this.initialize(); 方法之后的代码
QuartzInitializerListener.java执行完成,进入QuartzConfig.java 中的
schedulerFactoryBean(JobFactory jobFactory)
执行完
schedulerFactory.setQuartzProperties(quartzProperties());
后再一次实例化了一个相同的Scheduler,从而导致报错。
来源:CSDN
作者:Claire0118
链接:https://blog.csdn.net/Nancy50/article/details/104017171