javaspringspring-bootquartz-scheduler

How to avoid default Quartz Scheduler in Spring Boot application?


I'm setting up a Quartz Scheduler in Spring Boot. I want the scheduled Jobs to be able to inject Beans from the Application Context. For that I created my own Quartz Job Factory:

public final class MyQuartzJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) throws BeansException {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }

}

For keeping a reference to the Scheduler, I have a SchedulerManager Bean that initializes the Scheduler:

    public void initScheduler() {
        try (InputStream propsInputStream = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("my-quartz.properties")) {
            log.info("Creating Quartz scheduler...");
            Properties myQuartzProperties = new Properties();
            myQuartzProperties.load(propsInputStream);

            SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
            schedulerFactory.setQuartzProperties(myQuartzProperties);
            schedulerFactory.afterPropertiesSet();
            schedulerFactory.setJobFactory(quartzJobFactory);

            this.scheduler = schedulerFactory.getScheduler();
            this.schedulingConfigMap = new HashMap<>();

            log.info("Quartz scheduler created.");
        } catch (Exception e) {
            log.error("Unable to create Quartz scheduler", e);
            throw new RuntimeException("Scheduler extension initialization error", e);
        }
    }

I do the initialization of the Scheduler once the context has been initialized:

public class MyLifecycleListener implements ServletContextListener {

    private final SchedulerManager schedulerManager;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        schedulerManager.initScheduler();
    }

}

The quartz configuration file is as follows:

org.quartz.scheduler.instanceName=MyQuartzScheduler

org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=12
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
org.quartz.jobStore.misfireThreshold=60000

When starting the application I see my Quartz Scheduler is initialized, but the problem is that I see a second Quartz Scheduler initialized (maybe a Spring default one?).

Mine: 'MyQuartzScheduler' with 12 threads

2022-08-23 14:13:03.239  INFO 24788 --- [           main] org.quartz.core.QuartzScheduler          : Quartz Scheduler v.2.3.2 created.
2022-08-23 14:13:03.240  INFO 24788 --- [           main] org.quartz.simpl.RAMJobStore             : RAMJobStore initialized.
2022-08-23 14:13:03.241  INFO 24788 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'MyQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 12 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

2022-08-23 14:13:03.241  INFO 24788 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'MyQuartzScheduler' initialized from an externally provided properties instance.

Some milliseconds later, another one called 'quartzScheduler' with 10 threads.

2022-08-23 14:13:03.713  INFO 24788 --- [           main] org.quartz.core.QuartzScheduler          : Quartz Scheduler v.2.3.2 created.
2022-08-23 14:13:03.713  INFO 24788 --- [           main] org.quartz.simpl.RAMJobStore             : RAMJobStore initialized.
2022-08-23 14:13:03.713  INFO 24788 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

2022-08-23 14:13:03.713  INFO 24788 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.

How can I disable this second one?

Sample project can be found here: https://github.com/dajoropo/spring-quartz-demo/tree/stackoverflow_question_73459083


Solution

  • The easiest way is to use the Spring configured scheduler instead of doing it yourself. Move the quartz.properties to the spring.quartz namespace in the application.properties.

    spring.quartz.scheduler-name=MyQuartzScheduler
    
    spring.quartz.properties.org.quartz.threadPool.threadCount=12
    spring.quartz.properties.org.quartz.threadPool.threadPriority=5
    spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
    
    spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
    

    To configure your own JobFactory write a SchedulerFactoryBeanCustomizer to configure it on the SchedulerFactoryBean.

    @Component
    class MySchedulerFactoryBeanCustomizer implements SchedulerFactoryBeanCustomizer {
    
      void customize(SchedulerFactoryBean schedulerFactoryBean) {
        schedulerFactoryBean.setJobFactory(new MyQuartzJobFactory());
      }
    }
    

    Now ditch the quartz.properties, MyLifecycleListener and the SchedulerManager and let Spring handle all that for you.