Configuring Quartz Scheduler with Spring
lukas Quartz and Spring
In our previous project we required a web application to poll a directory on the filesystem to check for any new files that needed processing. We were using Spring 2.0 and Hibernate and were looking for a timer or polling solution that could integrate nicely with these technologies and allow us access to our Spring configured beans. Step in Quartz.
Quartz is amongst others, (see http://opensymphony.com/quartz/) a scheduling component that lets you run external tasks from within your web application. “Yikes!” - you may think - running threads outside the container… but in fact Quartz is the most widely used framework to do just that. It has very nice Spring integration and enables us to write minimal code. Also, is instantiated via Spring itself and therefore has access to all Spring configured components. Some benefits of the integration are:
- The only code to write is the Job class (the actual task to run) - everything else is configured in the applicationContext.xml
- It lets us instantiate the Quartz scheduler from within Spring and at the same time that Spring is instantiated and therefore…
- It gives Quartz jobs access to any classes within the Spring context including implicit access to the SessionFactory (and therefore our DAOs) as well as any transaction wrappers used during declarative transaction handling.
Basically the processing of our job is implemented as follows:
- Implement a Quartz job by extending a QuartzJobBean and populate the executeInternal() method. This method will contain the actual business processing - in our case the following steps to process the files on the filesystem:
- Job reads input directory for new files
- The list of files is passed to a Handler class that processes them one by one. (This reads them in, parses and saves to the database. Once a file is successfully processed it is deleted from the incoming directory).
- After implementing the job above, we configure it as a bean inside Spring’s applicationContext.xml:
- the above job is configured and given a name:
<bean name="fileReaderJob" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.myapplication.scheduler.FileReaderJob"/> <property name="name" value="fileReaderJob"/> </bean>
- The job bean is attached as a jobDetail to a Quartz Trigger, for example a Cron-like trigger that fires every 30 minutes to check for new files:
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="fileReaderJob"/> <!-- run every 30 minutes --> <property name="cronExpression" value="0 0/30 * * * ?"/> </bean>
- Finally the cron trigger is wired up to the SchedulerFactoryBean which instantiates the scheduler and starts the job when the Spring Context is initialised.
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean
That’s it. Once the Spring Context is loaded during application start-up the scheduler starts and your trigger (and attached job) is run.
Setting JobBeans properties from a configuration file
One additional enhancement to the above was to move the cronExpression setting from the applicationContext.xml into a JFig config file. This way changes to this setting can be controlled through config.xml files rather than having to change applicationContext.xml. Again with Spring this is pretty easy to do:
1. Subclass the org.springframework.scheduling.quartz.CronTriggerBean
2. Override setCronExpression(String cronExpression) method to retrieve the expression from a config file:
public class ConfigurableCronTriggerBean extends CronTriggerBean { public void setCronExpression(String cronExpression) throws ParseException { //ignore the dummy value from the applicationContext and set the cron from Jfig super.setCronExpression(ConfigFactory.getConfig().getValue(”scheduler”,”cronExpression”)); } }
3. Replace the CronTriggerBean with the new class created above and set the cronExpression in the applicationContext with a dummy parameter:
<bean id="cronTrigger" class="com.myapplication.scheduler.ConfigurableCronTriggerBean"> <property name="jobDetail" ref="fileReaderJob" /> <property name="cronExpression" value=”dummy” /> <!–this will call setter in the trigger bean–> </bean>
Setting the cronExpression property is still required - this will force Spring to executes the setter for the cronExpression. The “dummy” value is irrelevant as it will be replaced with the value from the config anyway.
Initially I thought that the cronExpression property is not required but not having it declared as part of the CronTriggeBean did not actually execute the setter and the following exception was thrown:
[6/6/07 15:03:33:265 NZST] 00000013 SystemErr R Caused by: org.quartz.SchedulerException: Based on configured schedule, the given trigger will never fire.at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:745)at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:266) at org.springframework.scheduling.quartz.SchedulerFactoryBean.addTriggerToScheduler(SchedulerFactoryBean.java:846) at org.springframework.scheduling.quartz.SchedulerFactoryBean.registerJobsAndTriggers(SchedulerFactoryBean.java:776) at org.springframework.scheduling.quartz.SchedulerFactoryBean.afterPropertiesSet(SchedulerFactoryBean.java:599) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1175) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1145) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:427) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:144) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:160) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:276) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:360) at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:241) at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:184) at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
Posted in spring, programming |
No Comments »