Spring Boot Quartz Scheduler Integration
Configuring quartz scheduler in a spring boot application requires a number of steps. This tutorial assumes that you already have a simple spring boot application up and running. Please see this tutorial on developing spring boot applications with spring tool suite if you are not familiar with setting up a simple spring boot application.
The following example uses RAMJobStore for persisting quartz job. This is one of fastest jobstore implementations for storing quartz job schedules. RAMJobStore stores quartz job data in the memory and hence scheduling information is lost in case of a program crash. This may not be suitable for all use cases. However, we will use RAMJobStore in this tutorial to keep the configuration simple.
Step 1: Modify the build.gradle to add quartz and related dependencies. Please refer to the updated gradle file below for the dependency details,
1. build.gradle
buildscript { ext { springBootVersion = '1.5.2.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('org.springframework:spring-context-support') compile('org.springframework:spring-tx') compile('org.quartz-scheduler:quartz:2.2.1') testCompile('org.springframework.boot:spring-boot-starter-test') }
Step 2: Write the classes for the schedule job and the underlying application service. Following is a simple spring service used by the tutorial. It prints "Hello World!" in the console log. We will also write a quartz job to invoke this service.
2a. SimpleService.java
package com.quickprogrammingtips.springboot.services; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class SimpleService { private static final Logger LOG = LoggerFactory.getLogger(SimpleService.class); public void processData() { LOG.info("Hello World!"); } }
2b. SimpleJob.java
package com.quickprogrammingtips.springboot.jobs; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.springframework.beans.factory.annotation.Autowired; import com.quickprogrammingtips.springboot.services.SimpleService; public class SimpleJob implements Job{ @Autowired private SimpleService service; @Override public void execute(JobExecutionContext jobExecutionContext) { service.processData(); } }
Step 3: Create a custom SpringBeanJobFactory for enabling spring auto wiring for the SimpleJob and SimpleService written above. This is required because SpringBeanJobFactory manually instantiates job instances whenever a job needs to run. Hence spring autowiring needs to be manually applied on the job instance if we are using autowiring features in the classes invoked by job instance (SimpleJob and SimpleService above).
3. AutowiringSpringBeanJobFactory.java
package com.quickprogrammingtips.springboot; import org.quartz.spi.TriggerFiredBundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.quartz.SpringBeanJobFactory; public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private static final Logger LOG = LoggerFactory.getLogger(AutowiringSpringBeanJobFactory.class); private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) { beanFactory = context.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); LOG.info("create job instance"); beanFactory.autowireBean(job); return job; } }
Step 4: Create a spring bean java configuration class for configuring all the beans required for the quartz scheduler. We configure a job factory using the custom AutowiringSpringBeanJobFactory created above. We also setup the quartz scheduler using the SchedulerFactoryBean. Note how the SimpleJob is configured using JobDetailFactoryBean and SimpleTriggerFactoryBean. The quartz configuration is loaded from the quartz.properties in classpath. The frequency of job is configured using the spring property simplejob.frequency. We will set this value in application.properties.
4. SchedulerConfig.java
import java.io.IOException; import java.util.Properties; import org.quartz.JobDetail; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.spi.JobFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.quartz.JobDetailFactoryBean; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; import com.quickprogrammingtips.springboot.jobs.SimpleJob; @Configuration public class SchedulerConfig { private static final Logger LOG = LoggerFactory.getLogger(SchedulerConfig.class); @Bean public JobFactory jobFactory(ApplicationContext applicationContext) { AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); return jobFactory; } @Bean public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, Trigger simpleJobTrigger) throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setJobFactory(jobFactory); factory.setQuartzProperties(quartzProperties()); factory.setTriggers(simpleJobTrigger); LOG.info("starting jobs...."); return factory; } @Bean public SimpleTriggerFactoryBean simpleJobTrigger(@Qualifier("simpleJobDetail") JobDetail jobDetail, @Value("${simplejob.frequency}") long frequency) { LOG.info("simpleJobTrigger"); SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); factoryBean.setJobDetail(jobDetail); factoryBean.setStartDelay(0L); factoryBean.setRepeatInterval(frequency); factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); return factoryBean; } @Bean public Properties quartzProperties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } @Bean public JobDetailFactoryBean simpleJobDetail() { JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); factoryBean.setJobClass(SimpleJob.class); factoryBean.setDurability(true); return factoryBean; } }
Step 5: Modify the main spring boot application to load the SchedulerConfig.
5. SpringbootquartzdemoApplication
package com.quickprogrammingtips.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Import; @Import({ SchedulerConfig.class }) @SpringBootApplication public class SpringbootquartzdemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootquartzdemoApplication.class, args); } }
Step 6: Configure application.properties with simplejob.frequency entry. We set the job running every second!
6. application.properties
simplejob.frequency=1000
Step 7: Configure quartz.propertes with minimal quartz configuration.
7. quartz.properties
org.quartz.scheduler.instanceName=spring-boot-quartz-demo org.quartz.scheduler.instanceId=AUTO org.quartz.threadPool.threadCount=5 org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
Step 8: Run the application using the gradle bootRun task. If everything goes well, "Hello World!" will be printed on the console every second.