Sunday, 2 September 2012

Spring Quartz Tutorial

In this post I’ll show you how to use Quartz scheduling with a Spring application. Quartz can be easily integrated with Spring to invoke schedule tasks that call your existing business logic. One of the things I really like about Springs support for Quartz is that you don't have to write any Quartz specific code or amend your existing code to use Quartz scheduling. You simply configure Quartz alongside your existing bean definitions and point it at the logic that you'd like to invoke.

Technologies Used
Our Spring/Quartz example will be built as a stand alone Java application using the following.
  • Spring 3.1
  • Maven
  • Quartz 1.6
It is assumed that readers have a basic knowledge of Spring and Maven. To help you get an example running quickly I’ll add a link to the full source code at the bottom of this post. Once you download the Maven project simply import it into Eclipse and run a Maven build.

Creating a simple Project
We'll start off by creating a very simple Spring project like the one shown in figure 1.0 below.
Figure 1.0 - Project Structure
The main project components are described below.

  • src/main/java folder - this folder structure will contain our Java source. We'll have just 2 classes - TestsService.java and RunTest.java, both of which are defined below.
  • src/main/resources folder - this folder will contain our Spring and log4J configuration files.
Creating a Simple Service
We'll start off by creating a class containing a simple service method that we'll invoke with Quartz. The testServiceMethod method is very simple indeed and just logs out the current time - this will allow us to see exactly when the method is invoked by Quartz. The important thing to note is that this code is not coupled to Quartz in any way - it's a simple POJO. This means that you can integrate Quartz with your application and configure it to invoke existing code without making any changes to the code whatsoever. The service class is defined below.
 package com.blog.samples.quartz;  
   
 import java.util.Date;  
 import org.apache.log4j.Logger;  
 import org.springframework.stereotype.Service;  
   
 @Service  
 public class TestService  
 {  
      private static final Logger logger_c = Logger.getLogger(TestService.class);  
   
      public void testServiceMethod()  
      {  
           Date date = new Date();  
           logger_c.debug("test service method invoked: " + date.toString());  
      }  
 }  
Defining Spring/Quartz Configuration
Next we'll look at the Spring configuration and setting up Quartz in particular. I've explained the various bean configurations with comments below.
 <?xml version="1.0" encoding="UTF-8"?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
           xmlns:context="http://www.springframework.org/schema/context"  
           xsi:schemaLocation="http://www.springframework.org/schema/beans  
                                    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
                                    http://www.springframework.org/schema/context  
                                    http://www.springframework.org/schema/context/spring-context-3.0.xsd">  
      <!--  
           Our test service bean  
      -->  
      <bean id="testService" class="com.blog.samples.quartz.TestService"/>  
   
      <!--  
            Job Detail bean configuration specifies the target object (our service object defined above)  
            and the method we want to invoke on that object (testServiceMethod). The concurrent property  
            specifies whether or not multiple instances of this job can be invoked concurrently  
      -->  
   <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  
     <property name="targetObject" ref="testService" />  
     <property name="targetMethod" value="testServiceMethod" />  
     <property name="concurrent" value="false" />  
   </bean>  
   
      <!--  
           The cron trigger bean allows us to specify the job that we want to invoke (jobDetail above)  
           and a cron expression that defines when the job should be invoked. My configuration below  
           will be invoked every 10 seconds  
      -->  
   <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">  
     <property name="jobDetail" ref="jobDetail" />  
     <property name="cronExpression" value="0,10,20,30,40,50 * * * * ?" />  
   </bean>  
   
      <!--   
           The SchedulerFactoryBean takes a list of cron triggers - our example has just one   
           cron trigger but larger enterprise applications will typically have a number of different   
           cron trigger for different jobs. The quartz properties property allows you to specify   
           some specific quartz properties. In our simple example we tell Quartz not to check for updates    
      -->  
   <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
     <property name="triggers">  
       <list>  
         <ref bean="cronTrigger" />  
       </list>  
     </property>  
     <property name="quartzProperties">  
                <props>  
            <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>  
          </props>  
     </property>  
   </bean>  
   
 </beans>  
Creating a Test Class
Next we'll create a test class to run our Quartz application. This is a simple class with a main method that loads the spring application context from the classpath. Once the application context is loaded we sleep the thread for 100 seconds so as to allow the Quartz scheduler to run and invoke our service method. Our Quartz cron definition was set up to run every 10 seconds so we should see our service method called 10 times before our application context is closed and the application shuts down. Our test class is defined below.
 package com.blog.samples.quartz;  
   
 import org.apache.log4j.Logger;  
 import org.springframework.context.support.ClassPathXmlApplicationContext;  
   
 public class RunTest  
 {  
      private static final Logger logger_c = Logger.getLogger(RunTest.class);  
   
      public static void main (String [] args)  
      {  
           logger_c.debug("loading spring application context");  
           ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-config.xml");  
   
           try  
           {  
                /* sleep thread */  
                Thread.sleep(100000);  
           }  
           catch (InterruptedException ex)  
           {  
                logger_c.debug(ex);  
           }  
   
           /* close down spring application context */  
           applicationContext.stop();  
           logger_c.debug("exiting...");  
      }  
 }  
Running the Application
When we run the main method above we see the following output to the console.You'll see that our service method is invoked by Quartz exactly every 10 seconds as expected.
 DEBUG: [Sep-02 13:27:58,334] samples.quartz.RunTest - loading spring application context  
 INFO : [Sep-02 13:27:58,578] context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@314c194d: startup date [Sun Sep 02 13:27:58 BST 2012]; root of context hierarchy  
 INFO : [Sep-02 13:27:59,552] quartz.core.QuartzScheduler - Quartz Scheduler v.1.6.0 created.  
 INFO : [Sep-02 13:27:59,556] quartz.simpl.RAMJobStore - RAMJobStore initialized.  
 INFO : [Sep-02 13:27:59,556] quartz.impl.StdSchedulerFactory - Quartz scheduler 'org.springframework.scheduling.quartz.SchedulerFactoryBean#0' initialized from an externally provided properties instance.  
 INFO : [Sep-02 13:27:59,556] quartz.impl.StdSchedulerFactory - Quartz scheduler version: 1.6.0  
 INFO : [Sep-02 13:27:59,560] quartz.core.QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@5773ec72  
 INFO : [Sep-02 13:27:59,578] context.support.DefaultLifecycleProcessor - Starting beans in phase 2147483647  
 INFO : [Sep-02 13:27:59,579] scheduling.quartz.SchedulerFactoryBean - Starting Quartz Scheduler now  
 INFO : [Sep-02 13:27:59,579] quartz.core.QuartzScheduler - Scheduler org.springframework.scheduling.quartz.SchedulerFactoryBean#0_$_NON_CLUSTERED started.  
 DEBUG: [Sep-02 13:28:00,048] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:28:00 BST 2012  
 DEBUG: [Sep-02 13:28:10,024] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:28:10 BST 2012  
 DEBUG: [Sep-02 13:28:20,018] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:28:20 BST 2012  
 DEBUG: [Sep-02 13:28:30,014] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:28:30 BST 2012  
 DEBUG: [Sep-02 13:28:40,021] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:28:40 BST 2012  
 DEBUG: [Sep-02 13:28:50,013] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:28:50 BST 2012  
 DEBUG: [Sep-02 13:29:00,020] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:29:00 BST 2012  
 DEBUG: [Sep-02 13:29:10,020] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:29:10 BST 2012  
 DEBUG: [Sep-02 13:29:20,027] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:29:20 BST 2012  
 DEBUG: [Sep-02 13:29:30,018] samples.quartz.TestService - test service method invoked: Sun Sep 02 13:29:30 BST 2012  
 INFO : [Sep-02 13:29:39,583] context.support.DefaultLifecycleProcessor - Stopping beans in phase 2147483647  
 INFO : [Sep-02 13:29:39,587] quartz.core.QuartzScheduler - Scheduler org.springframework.scheduling.quartz.SchedulerFactoryBean#0_$_NON_CLUSTERED paused.  
 DEBUG: [Sep-02 13:29:39,590] samples.quartz.RunTest - exiting...  
Conclusion
The sample code in this tutorial is very simple indeed, but should provide you with the information required to get Quartz scheduling up and running within a Spring application. If you want to run this tutorial locally you can grab the full source code here https://docs.google.com/open?id=0B_SZOyniHfc1M0FDeW4tT0J1SWs.

12 comments:

  1. Hi,

    It is posible deploy a spring-quartz project in a cluster mode?
    The cluster has many nodes and each node is in a different location (is not possible to synchronize the clocks of the machines).

    http://quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigJDBCJobStoreClustering

    ReplyDelete
  2. Yes it is possible to set Quartz up in a cluster. I actually did this a few weeks ago in work. We had to deploy our solution onto a WebSphere cluster and to ensure that each Quartz job was only invoked on one node at any given time, I used a JDBCJobStore instead of the standard in memory job store(RAMJobStore). This means that the invocation of Quartz jobs are managed centrally in the database as opposed to be being managed in memory by each node in the cluster.

    In very simple terms, Quartz cluster support works by placing a lock on a Job in the database when the first node attempts to process that job. Subsequent nodes are not allowed to invoke the same the job as it is already being processed. This simple mechanism ensures that only one node in your cluster processes a scheduled task. This is the kind of behaviour we wanted in a cluster but I'm sure that other approaches can be taken. If you'd like I can put together a quick post on setting up Quartz in a cluster?

    ReplyDelete
    Replies
    1. Could you please provide a sample configuration for JDBCJobStore in spring? Thanks

      Delete
  3. Hi Brian,

    I have Quartz clustered with 2 nodes, running on 2 different machines.
    Both running as standalone programs.

    I'm using JDBCJobStore.
    org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DB2v7Delegate

    I have a job scheduled to run every 20 mins. (0 0/20 * * * ?) with cron trigger.

    Surprisingly, The job gets triggered twice given time interval. Once on each node, randomly

    It has run twice at these times yesterday. 8:20, 10:40, 14:20,20:00, 20:40.

    Please let me know, is there anything wrong?

    ReplyDelete
  4. Thank you for providing this sample. Most other Spring Quartz samples are way too complicated for me.

    ReplyDelete
  5. thank u so much for providing these examples.....

    ReplyDelete
  6. Thanks for the example, how can we use quartz and activemq together. I just want send message based on some crone time.

    ReplyDelete
    Replies
    1. Combing Quartz with JMS is pretty straight forward. You'll want to use the MethodInvokingJobDetailFactoryBean, that's the jobDetail bean in this tutorial, to invoke the JMSTemplate described in my JMS ActiveMQ tutorial. You should be able to get this functionality up and running very quickly by combining the code samples from both tutorials.

      Delete
  7. Thanks for the example and detailed comments.

    ReplyDelete
  8. Hi,
    Can you please provide me a code snap for scheduler for cluster environment.

    ReplyDelete
  9. Hai,

    I have Configured Quartz scheduler in my app with spring 3.2.2 as you explained above,it is working fine.

    But While stopping server/app i am getting some Memory Leaks like.....

    SEVERE: The web application [/app] appears to have started a thread named [schedularbeanfactoryobj_Worker-1] but has failed to stop it. This is very likely to create a memory leak.
    .
    .
    I have googled and try with some solutions but could not fixed this.

    How To Fix This,I need Some Help.

    Thanks & regards
    raju(rajumuddana@gmail.com)

    ReplyDelete