问题排查 Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerRepository.

孤街醉人 提交于 2020-01-22 06:39:06

一、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,从而导致报错。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!