Spring & Quartz Integration with Custom Annotation
If you want to see how to integrate Spring with Quartz you can refer to the Spring + Quartz + JavaMail Integration Tutorial.
As part of my pet project requirements I got to schedule the Jobs dynamically and I thought of the following 2 options:
1. Using Annotations for providing Job Metadata
2. Loading the Job Metadata from Database
For now I thought of going ahead with the 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:annotation-config> <context:component-scan base-package="com.sivalabs"></context:component-scan> <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. Use a Test Client to launch the context
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: Spring and Quartz Integration Using Custom Annotation from our JCG partner Siva at “My Experiments on Technology” Blog.
Hello Siva Prasad ..!
Thanks for above post its really potential code .
in my company I have a requirement like
1.I need to write the thread scheduler using quartz&spring which will trigger my API for every 5 sec .
Thanks for the great post
Is this post still relevant ?