I am Siva, a passionate java developer, open source enthusiast, blogger. I like to cover Java, Struts, Spring, Hibernate, Ajax Tutorials, How-To's and Best Practices. Sivaprasadreddy is a DZone MVB and is not an employee of DZone and has posted 35 posts at DZone. You can read more from them at their website. View Full User Profile

Spring and Quartz Integration Using Custom Annotation @QuartzJob

10.10.2011
| 10549 views |
  • submit to reddit

We know Spring has support for integrating with Quartz framework.
But as of now Spring supports only static xml declarative approach only.
If you want to see how to integrate Spring+Quartz you can refer:

http://sivalabs.blogspot.com/2011/05/spring-quartz-javamail-integration.html


As part of my pet project requirement I got to schedule the Jobs dynamically and I thought of following 2 options:
 1. Using Annotations for providing Job Metada
 2. Loading the Job Metadata from Database
 
For now I thought of going ahead with Annotation based approach and I want to integrate it with Spring as well.
Here is how I did it.

1. Create a Custom Annotation QuartzJob
   

package com.sivalabs.springsamples.jobscheduler;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
@Scope("prototype") public @interface QuartzJob { String name(); String group() default "DEFAULT_GROUP"; String cronExp(); }


2. Create an ApplicationListener to scan for all the Job implementation classes and schedule them using Quartz scheduler.

package com.sivalabs.springsamples.jobscheduler;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.JobDetailBean;

public class QuartJobSchedulingListener implements ApplicationListener<ContextRefreshedEvent>
{    
    @Autowired
    private Scheduler scheduler;
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event)
    {
        try
        {
            ApplicationContext applicationContext = event.getApplicationContext();
            List<CronTriggerBean> cronTriggerBeans = this.loadCronTriggerBeans(applicationContext);
            this.scheduleJobs(cronTriggerBeans);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private List<CronTriggerBean> loadCronTriggerBeans(ApplicationContext applicationContext)
    {
        Map<String, Object> quartzJobBeans = applicationContext.getBeansWithAnnotation(QuartzJob.class);
        Set<String> beanNames = quartzJobBeans.keySet();
        List<CronTriggerBean> cronTriggerBeans = new ArrayList<CronTriggerBean>();
        for (String beanName : beanNames)
        {
            CronTriggerBean cronTriggerBean = null;
            Object object = quartzJobBeans.get(beanName);
            System.out.println(object);
            try {
                cronTriggerBean = this.buildCronTriggerBean(object);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            if(cronTriggerBean != null)
            {
                cronTriggerBeans.add(cronTriggerBean);
            }
        }
        return cronTriggerBeans;
    }
    
    public CronTriggerBean buildCronTriggerBean(Object job) throws Exception
    {
        CronTriggerBean cronTriggerBean = null;
        QuartzJob quartzJobAnnotation = AnnotationUtils.findAnnotation(job.getClass(), QuartzJob.class);
        if(Job.class.isAssignableFrom(job.getClass()))
        {
            System.out.println("It is a Quartz Job");
            cronTriggerBean = new CronTriggerBean();
            cronTriggerBean.setCronExpression(quartzJobAnnotation.cronExp());                
            cronTriggerBean.setName(quartzJobAnnotation.name()+"_trigger");
            //cronTriggerBean.setGroup(quartzJobAnnotation.group());
            JobDetailBean jobDetail = new JobDetailBean();
            jobDetail.setName(quartzJobAnnotation.name());
            //jobDetail.setGroup(quartzJobAnnotation.group());
            jobDetail.setJobClass(job.getClass());
            cronTriggerBean.setJobDetail(jobDetail);            
        }
        else
        {
            throw new RuntimeException(job.getClass()+" doesn't implemented "+Job.class);
        }
        return cronTriggerBean;
    }
    
    protected void scheduleJobs(List<CronTriggerBean> cronTriggerBeans)
    {
        for (CronTriggerBean cronTriggerBean : cronTriggerBeans) {
            JobDetail jobDetail = cronTriggerBean.getJobDetail();
            try {
                scheduler.scheduleJob(jobDetail, cronTriggerBean);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }            
        }
    }
}

3. Create a customized JobFactory to use Spring beans as Job implementation objects.

 

package com.sivalabs.springsamples.jobscheduler;

import org.quartz.Job;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

public class SpringQuartzJobFactory extends SpringBeanJobFactory
{
	@Autowired
	private ApplicationContext ctx;

	@Override
	protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception 
	{
	    @SuppressWarnings("unchecked")
		Job job = ctx.getBean(bundle.getJobDetail().getJobClass());
	    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
	    MutablePropertyValues pvs = new MutablePropertyValues();
	    pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
	    pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
	    bw.setPropertyValues(pvs, true);
	    return job;
	}	
}


4. Create the Job implementation classes and Annotate them using @QuartzJob

package com.sivalabs.springsamples.jobscheduler;

import java.util.Date;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;

@QuartzJob(name="HelloJob", cronExp="0/5 * * * * ?")
public class HelloJob extends QuartzJobBean
{
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException
{
System.out.println("Hello Job is running @ "+new Date());
System.out.println(this.hashCode());
}
}


5. Configure the SchedulerFactoryBean and QuartJobSchedulingListener in applicationContext.xml

<beans>
    <context:annotation-config/>
    <context:component-scan base-package="com.sivalabs"/>
    
    <bean class="com.sivalabs.springsamples.jobscheduler.QuartJobSchedulingListener"></bean>
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobFactory">
<bean class="com.sivalabs.springsamples.jobscheduler.SpringQuartzJobFactory"></bean>
</property>
</bean> </beans>

6. Test Client

 

package com.sivalabs.springsamples;

import org.quartz.Job;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.sivalabs.springsamples.jobscheduler.HowAreYouJob;
import com.sivalabs.springsamples.jobscheduler.InvalidJob;

public class TestClient
{
    public static void main(String[] args)
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(context);        
    }

}

Reference : http://sivalabs.blogspot.com/2011/10/spring-and-quartz-integration-using.html

Published at DZone with permission of Sivaprasadreddy Katamreddy, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Bozhidar Bozhanov replied on Wed, 2011/10/12 - 2:12am

That's very similar to my solution of the same problem http://java.dzone.com/articles/making-spring-and-quartz-work . It really seems spring should consider putting such support in their code - it is obviously a requirement in many cases.

Sivaprasadreddy... replied on Wed, 2011/10/12 - 2:29am in response to: Bozhidar Bozhanov

Hi,

I went through your post and yeah its almost similar.

Actually I thought of having a plugin point, say 'JobConfigProvider' and provides extension points like 'AnnotationBasedJobConfiProvider' or 'JdbcBasedJobConfigProvider'.

 AnnotationBasedJobConfiProvider will scan for Quartz jobs annotated with @QuartzJob and schedule them as I explained in article.

 JdbcBasedJobConfigProvider will get the Job configuration details from databaseand schedule them.

I am planning to develop a Simple JobMonitor tool through which we can monitor Jobs, (re)schedule them or terminate the executing Jobs etc. It seems you are also working on similar requirement. I would be happy if you want me to join you on this.

Thanks,

Siva

Carla Brian replied on Fri, 2012/04/20 - 11:27am

Quartz is an open source job-scheduling framework written entirely in Java and designed for use in both J2SE and J2EE applications. It offers great flexibility without sacrificing simplicity. - Dr Paul Perito

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.