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.

Friday, 23 September 2016

1. Spring Quartz Scheduler - Using MethodInvokingJobDetailFactoryBean

In this tutorial, we will see how to schedule jobs using "Quartz Scheduler" with Spring. There are few utility classes Spring provides to configure jobs and trigger execution based on schedule.

Following are tools/library versions used:
  • Spring 4.3.3.RELEASE
  • Quartz 2.2.3
  • Maven 3
  • JDK 1.8
  • Eclipse Mars
Create maven based project and add below dependencies in pom.xml for Spring and Quartz.
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
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>in.blogspot.ashish4java</groupId>
  <artifactId>SimpleSpringQuartzScheduler</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>SimpleSpringQuartzScheduler</name>
  <description>Simple Spring Quartz Scheduler tutorial with method invocation and simple trigger</description>
      <properties>
        <springframework.version>4.3.3.RELEASE</springframework.version>
        <quartz.version>2.2.3</quartz.version>
    </properties> 
    <dependencies>    
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>${quartz.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${springframework.version}</version>
        </dependency>
    </dependencies>
</project>


There are two ways to configure jobs using Spring. We will look into 'MethodInvokingJobDetailFactoryBean' which is used to invoke method. This is just to schedule invocation of single method call.

Job class:
This class contains method which needs to be invoked as per schedule.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
package in.blogspot.ashish4java.simplespringquartzexample.scheduler;

public class MyJob {

 
 public void task() {
  System.out.println("Task execution in progress...");
 }

}

Spring Configuration:
Now, configure MyJob bean in Spring configuration file along with "MethodInvokingJobDetailFactoryBean" bean which takes properties of 'targetObject' and 'targetMethod'
1
2
3
4
5
6
7
8
<bean id="myJob"
  class="in.blogspot.ashish4java.simplespringquartzexample.scheduler.MyJob" />

 <bean id="methodInvocationConfig"
  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  <property name="targetObject" ref="myJob" />
  <property name="targetMethod" value="task"></property>
 </bean>

Job Trigger:
There are two types of triggers 
1. Simple Trigger which can be configured to invoke for certain number of times with delay in between each execution.
2. Cron Trigger which takes parameter like Cron job and can be scheduled with more control over schedule.

We will configure using Simple Trigger:
1
2
3
4
5
6
 <bean id="simpleTrigger"
  class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
  <property name="jobDetail" ref="methodInvocationConfig" />
  <property name="repeatInterval" value="1000" />
  <property name="repeatCount" value="3" />
 </bean>


Now, let's put together this job and trigger:
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="simpleTrigger" />
   </list>
  </property>
  <property name="jobDetails">
   <list>
    <ref bean="methodInvocationConfig" />
   </list>
  </property>
 </bean>


Complete Spring configuration 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
<?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 id="myJob"
  class="in.blogspot.ashish4java.simplespringquartzexample.scheduler.MyJob" />

 <bean id="methodInvocationConfig"
  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  <property name="targetObject" ref="myJob" />
  <property name="targetMethod" value="task"></property>
 </bean>

 <bean id="simpleTrigger"
  class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
  <property name="jobDetail" ref="methodInvocationConfig" />
  <property name="repeatInterval" value="1000" />
  <property name="repeatCount" value="3" />
 </bean>

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

</beans>


Now, just load this Spring configuration file and check if task method gets invoked.

Main Client:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package in.blogspot.ashish4java.simplespringquartzexample;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainClient {

 public static void main(String[] args) {

  new ClassPathXmlApplicationContext("spring.xml");

 }

}

Result:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Sep 23, 2016 11:45:48 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1099f62: startup date [Fri Sep 23 23:45:48 IST 2016]; root of context hierarchy
Sep 23, 2016 11:45: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 23, 2016 11:45:49 PM org.springframework.context.support.DefaultLifecycleProcessor start
INFO: Starting beans in phase 2147483647
Sep 23, 2016 11:45:49 PM org.springframework.scheduling.quartz.SchedulerFactoryBean startScheduler
INFO: Starting Quartz Scheduler now
Task execution in progress...
Task execution in progress...
Task execution in progress...
Task execution in progress...

We scheduled job to be repeated 3 times plus original execution so total job was executed 4 times with delay of 1 seconds between each run.

This is very simple method invocation job scheduler using Spring and Quartz. But if we need to pass some parameters to job or trigger jobs based on exact date/time then we need to use more complex job scheduler which we will see in next tutorial.