
Quartz is an excellent, open-source scheduler which provides many enterprise features such as job persistence to a variety of job store implementations (e.g., RAM, JDBC, etc.), transactions and clustering. Spring offers good integration for Quartz and provides some nice abstractions for using it within the IoC container such as MethodInvokingJobDetailFactoryBean which allows you to use any old bean/method as a JobDetail.
One thing is the Spring documentation and most examples I’ve found in my travels have pointed toward the scenario where all your JobDetails and Triggers are defined “statically” in the Spring configuration files, not at runtime. My use case is slightly different, I need to create these Triggers at runtime. Some posts like this one suggests using a dependency-injected factory which will both create and wire the Triggers at runtime with dependencies, this is a valid solution but violates the IoC principle.
In a previous post I showed how to use Spring prototype scoped beans with dependency injection. In this post I’d like to show how the same notion can be applied to create prototype-scoped Quartz Triggers and JobDetails with Spring.
Let’s start by defining the scheduler using a SchedulerFactoryBean to create an instance of the scheduler:
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="jobFactory"> <bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory" /> </property> <property name="configLocation" value="classpath:quartz.properties" /> <property name="waitForJobsToCompleteOnShutdown" value="true" /> <property name="triggerListeners"> <list> <ref bean="myTriggerListener" /> </list> </property> <property name="schedulerContextAsMap"> <map> <entry key="myServiceBean" value-ref="myServiceBean" /> </map> </property> <property name="dataSource" ref="datasource" /> </bean>
Notice how myServiceBean is wired into the schedulerContextAsMap property. In its documentation Spring suggests that for persistent jobs you wire your beans this way and not via the JobDataMap, the “state context” mechanism for jobs.
So now I can get to work and define my Trigger and JobDetail prototype factories:
<bean id="jobTriggerFactory"
class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName">
<idref local="jobTrigger" />
</property>
</bean>
<bean id="jobTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"
scope="prototype">
<property name="group" value="myJobs" />
<property name="description" value="myDescription" />
<property name="repeatInterval" value="${myproperties.repeatinterval}" />
</bean>
<bean id="jobDetailFactory"
class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName">
<idref local="jobDetail" />
</property>
</bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"
scope="prototype">
<property name="jobClass" value="org.echotech.scheduler.MyJob" />
<property name="volatility" value="false" />
<property name="durability" value="false" />
<property name="requestsRecovery" value="true" />
</bean>
<bean id="schedulerManager" class="org.echotech.scheduler.MySchedulerManagerImpl">
<constructor-arg ref="quartzScheduler" />
<constructor-arg ref="jobDetailFactory" />
<constructor-arg ref="jobTriggerFactory" />
</bean>
The code for MySchedulerManagerImpl shows how to register a new Job with the scheduler using the provided factories.
public class MySchedulerManagerImpl {
private final Scheduler scheduler;
private final ObjectFactory<SimpleTrigger> jobTriggerFactory;
private final ObjectFactory<JobDetail> jobDetailFactory;
public MySchedulerManagerImpl(final Scheduler scheduler,
final ObjectFactory<JobDetail> jobDetailFactory,
final ObjectFactory<SimpleTrigger> jobTriggerFactory) {
Assert.notNull(scheduler, "scheduler cannot be null");
Assert.notNull(jobDetailFactory, "jobDetailFactory cannot be null");
Assert.notNull(jobTriggerFactory, "jobTriggerFactory cannot be null");
this.scheduler = scheduler;
this.jobDetailFactory = jobDetailFactory;
this.jobTriggerFactory = jobTriggerFactory;
}
public void scheduleNewJob(final Map<String, String> parameters,
final String name) {
// create a trigger
SimpleTrigger trigger = jobTriggerFactory.getObject();
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setStartTime(new Date());
trigger.setName(name);
// create job details
JobDetail jobDetail = jobDetailFactory.getObject();
jobDetail.setName(name);
jobDetail.setGroup(trigger.getGroup());
jobDetail.getJobDataMap().putAll(parameters);
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
// handle this, either wrap and re-throw with runtime variant or do
// something meaningful
}
}
}
This way you can decide what JobDetail and Trigger parameters you want to be defined in the Spring container (values can be externalized to property files, JNDI or other sources using the common Spring abstractions) and what is determined at runtime.
Hope this helps, happy scheduling
No Trackbacks
You can leave a trackback using this URL: http://techo-ecco.com/blog/quartz-and-spring-integration/trackback/
5 Comments
Hi.
I am highly impressed by this blog. This gives a lot of information about Quartz with Spring. I have also visited following link i.e. giving similarly good information about Quartz framework and its integration with both JSP, Servlet and also with Spring Framework. The links are:
Quartz integration with JSP,Servlet:
http://jksnu.blogspot.com/2011/03/quartz-framework-implementation-with.html
Quartz integration with Spring:
http://jksnu.blogspot.com/
Hello, thank you for a great post – it definitely filled in a hole in the Spring + Quartz documentation.
i have tried using your approach to defining properties of triggers dynamically at run time. All works great, except for the reference to the “myServiceBean”. I tried to store a reference to my custom service bean the same way, but when I try to retrieve it later on in the Job – it is always NULL.
Here is how I wired it into the quartz scheduler:
And this is how I am trying to retrieve it in my Job class:
public class EMailJob implements Job {
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobDataMap dataMap = context.getMergedJobDataMap();
MailService mailService = (MailService)dataMap.get(“mailService”);
if (mailService == null) {
logger.error(“error scheduling job: mailService = NULL”);
throw new JobExecutionException(“error executing job: mailService = NULL”);
}
…
and it is always NULL…
Any idea why this is happening?
thanks!
Marina
Have you tried using the “jobDataAsMap” property on your job bean? please see the reference documentation for more details: http://static.springsource.org/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-quartz
@Marina in order to access the service (myService) that was stored under schedulerContextAsMap use the following code:
@Override
public void execute(final JobExecutionContext jobExecutionContext) throws JobExecutionException {
…
MailService mailService = (MailService)
jobExecutionContext.getScheduler().getContext().get(“mailService”);
…
}
Hi,
Thank you for the post. I would like to know the pros of using spring integrated with quartz. Can you please figure out some cons also. I would be highly obliged for the reply.