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 Spring + Quartz Integration .
As part of my pet project requirement I got to schedule the Jobs dynamically and I though 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
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);
}
}
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
good~thank you for sharing~
Siva,
Can you tell me which version of Quartz have you used? I was trying to integrate Quartz 2.x with Spring 3.x and ran into problems, it seems there are known bugs. So, I want to downgrade Quartz to the version you have used and check.
Rahul,
Try Spring 3.x with Quartz 1.8.x That resolved my issues.
Cheers,
Ed
Hi,
I have been trying with spring-version 4.3.1 and quartz 2.2.3 without any luck. Does anybody tried this combination?
Help appreciated!
Cheers
Lipu