Quartz with Spring Boot, Quartz JobListener Example - How to keep track the status of the running job?
Hello everyone, today we will show you how to configure Quartz with Spring Boot and how to keep track the status of the running job.
- Spring Boot 2.6.7
- Java 17
- Quartz Scheduler
- Quartz JobListener
- RAMJobStore for persisting quartz job.
- Maven
Quartz is a richly featured, open-source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system.
Listeners are objects that you create to perform actions based on events occurring within the scheduler. As you can probably guess, TriggerListeners receive events related to triggers, and JobListeners receive events related to jobs.
RAMJobStore is the simplest JobStore to use. It keeps all of its data in RAM.
Final Project Directory:
Maven[pom.xml]
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.7</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.knf.dev.demo</groupId> <artifactId>spring-quartz-joblistener-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-quartz-joblistener-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
</dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
Service [TestService.java]
package com.example.demo.service;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;
@Servicepublic class TestService {
private static final Logger LOG = LoggerFactory. getLogger(TestService.class);
public void processData() { LOG.info("Inside Test Service"); }}
TestService is a simple service which will prints "Inside Test Service" in the console log. We will also write a quartz job to invoke this service.
Job[MySimpleJob.java]
package com.example.demo.job;
import org.quartz.Job;import org.quartz.JobExecutionContext;import org.springframework.beans.factory.annotation.Autowired;import com.example.demo.service.TestService;
public class MySimpleJob implements Job {
@Autowired private TestService service;
@Override public void execute(JobExecutionContext jobExecutionContext) { service.processData(); }}
SpringBeanJobFactory[AutowiringSpringBeanJobFactory.java]
package com.example.demo.config;
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 "+bundle.getJobDetail(). getKey().getName()+" instance"); beanFactory.autowireBean(job); return job; }}
Create a custom SpringBeanJobFactory for enabling spring auto wiring for the MySimpleJob and TestService.
SchedulerConfig.java
package com.example.demo.config;
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.example.demo.job.MySimpleJob;
@Configurationpublic class SchedulerConfig {
private static final Logger LOG = LoggerFactory. getLogger(SchedulerConfig.class);
@Bean public JobDetailFactoryBean mySimpleJob() {
var factoryBean = new JobDetailFactoryBean(); factoryBean.setJobClass(MySimpleJob.class); factoryBean.setDurability(true); return factoryBean; }
@Bean public JobFactory jobFactory (ApplicationContext applicationContext) {
var jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); return jobFactory; }
@Bean public SchedulerFactoryBean schedulerFactoryBean (JobFactory jobFactory, Trigger simpleJobTrigger) throws IOException {
var factory = new SchedulerFactoryBean(); factory.setJobFactory(jobFactory); factory.setQuartzProperties(quartzProperties()); factory.setTriggers(simpleJobTrigger); LOG.info("starting jobs...."); return factory; }
@Bean public SimpleTriggerFactoryBean simpleJobTrigger (@Qualifier("mySimpleJob") JobDetail jobDetail, @Value("${mysimplejob.frequency}") long frequency) {
LOG.info("mySimpleJobTriggered");
var 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 {
var propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean. setLocation(new ClassPathResource("/quartz.properties")); propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); }}
JobListener[GlobalJobListener.java]
package com.example.demo.listener;
import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.quartz.JobListener;import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class GlobalJobListener implements JobListener {
private static final String TRIGGER_LISTENER_NAME = "GlobalJobListener"; private static final Logger LOG = LoggerFactory.getLogger(GlobalJobListener.class);
@Override public String getName() { return TRIGGER_LISTENER_NAME; }
@Override public void jobToBeExecuted(JobExecutionContext context) {
var triggerName = context.getTrigger().getKey().toString(); var jobName = context.getJobDetail().getKey().toString(); LOG.info("trigger : " + triggerName + " is going to fire"); LOG.info("job : " + jobName + "is going to fire");
}
@Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
var triggerName = context.getTrigger().getKey().toString(); var jobName = context.getJobDetail().getKey().toString(); LOG.info("trigger : " + triggerName + " is fired"); LOG.info("job : " + jobName + " is fired");
}
@Override public void jobExecutionVetoed(JobExecutionContext context) { // TODO Auto-generated method stub
}}
Listeners are objects that you create to perform actions based on events occurring within the scheduler. As you can probably guess, TriggerListeners receive events related to triggers, and JobListeners receive events related to jobs.
JobListenerConfig.java
package com.example.demo.listener;
import javax.annotation.PostConstruct;import org.quartz.SchedulerException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.quartz.SchedulerFactoryBean;import org.springframework.stereotype.Component;
@Componentpublic class JobListenerConfig {
@Autowired private SchedulerFactoryBean schedulerFactoryBean;
@PostConstruct public void addListeners() throws SchedulerException {
schedulerFactoryBean.getScheduler(). getListenerManager().addJobListener(new GlobalJobListener()); }}
Registering listener with the scheduler.
application.properties
mysimplejob.frequency=500
quartz.properties
org.quartz.scheduler.instanceName=spring-quartz-demoorg.quartz.scheduler.instanceId=AUTOorg.quartz.threadPool.threadCount=10org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Import;
import com.example.demo.config.SchedulerConfig;
@Import({ SchedulerConfig.class })@SpringBootApplicationpublic class DemoApplication {
public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}
Local Setup and Run the application
Step1: Download or clone the source code to a local machine. - Click here
Step2: mvn clean install
Step3: Run the Spring Boot application - mvn spring-boot:run