Saturday, 24 September 2016

2. Spring Quartz Scheduler - Using QuartzJobBean

In previous tutorial, we have seen simple example of method invocation job execution using Quartz Scheduler with Spring configuration. But in real world, we need more than just simple method invocation. We need complex job scheduling where some input parameters/data may needs to be passed.

In this tutorial, we will see how to configure complex job which takes input data and will schedule that using Cron trigger to schedule to run periodically at fixed times, dates or intervals.

If I need to execute job which takes list of Strings as input and append them together with some delimiter and and returns complete string.

First let's create input data class which contains list of Strings,
MyJobInput.java:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package in.blogspot.ashish4java.complexspringquartzexample.scheduler;

import java.util.List;

public class MyJobInput {

 private List<String> processData;

 public List<String> getProcessData() {
  return processData;
 }

 public void setProcessData(List<String> processData) {
  this.processData = processData;
 }
}

Bean configuration:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<bean name="jobInputData"
  class="in.blogspot.ashish4java.complexspringquartzexample.scheduler.MyJobInput">
  <property name="processData">
   <list value-type="java.lang.String">
    <value>This</value>
    <value>is</value>
    <value>Spring</value>
    <value>Quartz</value>
    <value>example</value>
   </list>
  </property>
 </bean>

MyJob.java:
This class contains main job execution logic. It extends class QuartzJobBean. QuartzJobBean is simple implementation Job interface from Quartz and have two methods execute and executeInternal. execute method delegates call to executeInternal.

executeInternal is abstract method which is implemented as below,
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package in.blogspot.ashish4java.complexspringquartzexample.scheduler;

import java.util.List;

import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class MyJob extends QuartzJobBean {

 @Override
 protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

  StringBuilder result = new StringBuilder();

  System.out.println("---------------- JOB STARTS ----------------");
  System.out.println("Job: Previous Fire Time -" + context.getPreviousFireTime());
  System.out.println("Job: Scheduled Fire Time -" + context.getScheduledFireTime());
  System.out.println("Job: Current Fire Time -" + context.getFireTime());
  System.out.println("Job: Next Fire Time -" + context.getNextFireTime());

  JobDetail jobDetail = context.getJobDetail();
  MyJobInput inputData = (MyJobInput) jobDetail.getJobDataMap().get("jobState");
  List&lt;String> processData = inputData.getProcessData();

  if (processData != null && !processData.isEmpty()) {
   for (String raw : processData) {
    result = result.append(raw).append("--");
   }
  } else {
   result = new StringBuilder("NO INPUT DATA");
  }
  System.out.println("Result of this job execution is -" + result);
  System.out.println("---------------- JOB ENDS ----------------");
  System.out.println();
 }

}

Note down the use of JobDetails and JobDataMap to pass values to job execution.

Bean Configuration:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<bean name="myJob"
  class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
  <property name="jobClass"
   value="in.blogspot.ashish4java.complexspringquartzexample.scheduler.MyJob" />
  <property name="jobDataAsMap">
   <map>
    <entry key="jobState" value-ref="jobInputData" />
   </map>
  </property>
  <property name="durability" value="true" />
 </bean>


durability specify the job's durability, i.e. whether it should remain stored in the job store even if no triggers point to it anymore.

Trigger: 
As discussed earlier, let's trigger this job using cron expression to execute every 3 seconds.

1
2
3
4
5
 <bean id="cronTrigger"
  class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
  <property name="jobDetail" ref="myJob" />
  <property name="cronExpression" value="0/3 * * * * ?" />
 </bean>

Now, last remaining part is to stitch together trigger and jobDetails as below,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
   <list>
    <ref bean="cronTrigger" />
   </list>
  </property>
  <property name="jobDetails">
   <list>
    <ref bean="myJob" />
   </list>
  </property>
 </bean>

Complete spring.xml file:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">


 <bean name="myJob"
  class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
  <property name="jobClass"
   value="in.blogspot.ashish4java.complexspringquartzexample.scheduler.MyJob" />
  <property name="jobDataAsMap">
   <map>
    <entry key="jobState" value-ref="jobInputData" />
   </map>
  </property>
  <property name="durability" value="true" />
 </bean>
 <bean name="jobInputData"
  class="in.blogspot.ashish4java.complexspringquartzexample.scheduler.MyJobInput">
  <property name="processData">
   <list value-type="java.lang.String">
    <value>This</value>
    <value>is</value>
    <value>Spring</value>
    <value>Quartz</value>
    <value>example</value>
   </list>
  </property>
 </bean>

 <bean id="cronTrigger"
  class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
  <property name="jobDetail" ref="myJob" />
  <property name="cronExpression" value="0/3 * * * * ?" />
 </bean>

 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
   <list>
    <ref bean="cronTrigger" />
   </list>
  </property>
  <property name="jobDetails">
   <list>
    <ref bean="myJob" />
   </list>
  </property>
 </bean>

</beans>

Main Client to load spring configuration and execute this job:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package in.blogspot.ashish4java.complexspringquartzexample;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainClient {

 public static void main(String[] args) {

  new ClassPathXmlApplicationContext("spring.xml");

 }

}

Output:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Sep 24, 2016 2:01:48 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1099f62: startup date [Sat Sep 24 14:01:48 IST 2016]; root of context hierarchy
Sep 24, 2016 2:01:48 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Sep 24, 2016 2:01:49 PM org.springframework.context.support.DefaultLifecycleProcessor start
INFO: Starting beans in phase 2147483647
Sep 24, 2016 2:01:49 PM org.springframework.scheduling.quartz.SchedulerFactoryBean startScheduler
INFO: Starting Quartz Scheduler now
---------------- JOB STARTS ----------------
Job: Previous Fire Time -null
Job: Scheduled Fire Time -Sat Sep 24 14:01:51 IST 2016
Job: Current Fire Time -Sat Sep 24 14:01:51 IST 2016
Job: Next Fire Time -Sat Sep 24 14:01:54 IST 2016
Result of this job execution is -This--is--Spring--Quartz--example--
---------------- JOB ENDS ----------------

---------------- JOB STARTS ----------------
Job: Previous Fire Time -Sat Sep 24 14:01:51 IST 2016
Job: Scheduled Fire Time -Sat Sep 24 14:01:54 IST 2016
Job: Current Fire Time -Sat Sep 24 14:01:54 IST 2016
Job: Next Fire Time -Sat Sep 24 14:01:57 IST 2016
Result of this job execution is -This--is--Spring--Quartz--example--
---------------- JOB ENDS ----------------

---------------- JOB STARTS ----------------
Job: Previous Fire Time -Sat Sep 24 14:01:54 IST 2016
Job: Scheduled Fire Time -Sat Sep 24 14:01:57 IST 2016
Job: Current Fire Time -Sat Sep 24 14:01:57 IST 2016
Job: Next Fire Time -Sat Sep 24 14:02:00 IST 2016
Result of this job execution is -This--is--Spring--Quartz--example--
---------------- JOB ENDS ----------------

This shows job was triggered every 3 seconds and was getting list of Strings as input.

This approach of using Spring and quartz scheduler have one drawback if used without database to store job metadata which quartz provides. If this job execution is running on Clustered environment then without database, job will be executed on each of managed server as per trigger schedule. Many of times, we don't want same job getting executed from multiple servers. To overcome this, we have to use database to store job metadata. Quartz provides this facility along with loadbalancer. So spring + Quartz + Database needs to be used.

If there cache on each of managed server which needs to be refreshed at regular interval then Spring + Quartz Scheduler combination could suffice.

This is all about Spring Quartz Scheduler tutorial.