Wednesday, June 20, 2007

Accessing Spring beans from Quartz jobs

The Spring Framework integrates with the Quartz scheduler in a way that makes Quartz much easier to use. Although in order to use Spring beans with your Quartz jobs you have to deviate slightly from the usual Spring "dependency injection" way of doing things. According to the Spring API this is necessary because Quartz itself is responsible for the lifecycle of its Jobs.

I was recently refactoring my use of Quartz and Spring in my feed aggregator web application. Rather than explain the internal workings of my application at this time, I will explain some features I discovered with reference to James Goodwill's recent simple example of using Quartz and Spring together. James shows how a "cron style" job can easily be created by configuring Quartz Job, trigger, SchedulerFactoryBean and loading up the application context. In James' example the Spring application context would look something like this:


<beans>
<!-- Define the Job Bean that will be executed. Our bean is named in the jobClass property. -->
<bean name="myJob" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.gsoftware.common.util.MyJob"/>
</bean>

<!-- Associate the Job Bean with a Trigger. Triggers define when a job is executed. -->
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="myJob"/>
<property name="startDelay" value="2000"/>
<property name="repeatInterval" value="10000"/>
</bean>

<!-- A list of Triggers to be scheduled and executed by Quartz -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyy name="triggers">
<list>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
</beans>

Great stuff! You can pass static data into the Quartz job via the JobDetailBean using the JobDataMap mechanism but AFAICT you should not pass Spring beans through this means.

So what if I want my job to be able to access other Spring resources like data access layers etc.? Let us assume I have a data access object layer configured elsewhere in my Spring config (like the example below) and I want my Quartz job to be able to access it.


<!-- A DAO bean which itself may have dependencies on data sources and other stuff -->
<bean name="daoAccess" class="com.someplace.daoImpl">
<property name="dataSource">
...yadda..yadda..yadda...
</property>
</bean>

I discovered via the Quartz Method Invocation on Beans post on the Spring forum that you can pass a reference to the Spring application context via the SchedulerFactoryBean. Like the example shown below:


<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyy name="triggers">
<list>
<ref bean="simpleTrigger"/>
</list>
</property>
<property name="applicationContextSchedulerContextKey">
<value>applicationContext</value>
</property>
</bean>

You can then access the Spring application context inside the Quartz job. This means you can then access any identifiable beans, like the data access object layer bean, from the application context.


public class MyJob implements Job {

private static final String APPLICATION_CONTEXT_KEY = "applicationContext";

public void execute(JobExecutionContext context) throws JobExecutionException {
ApplicationContext appCtx = getApplicationContext(context);
MyDAO dao = (MyDAO) appCtx.getBean("daoAccess");
// place rest of your Job code here
System.out.println("EXECUTING QUARTZ JOB");
}

private ApplicationContext getApplicationContext(JobExecutionContext context )
throws Exception {
ApplicationContext appCtx = null;
appCtx = (ApplicationContext)context.getScheduler().getContext().get(APPLICATION_CONTEXT_KEY);
if (appCtx == null) {
throw new JobExecutionException(
"No application context available in scheduler context for key \"" + APPLICATION_CONTEXT_KEY + "\"");
}
return appCtx;
}
}

I mentioned before that I am using Spring and Quartz inside a web application. In this case I am loading the Spring application context via Spring's ContextLoaderListener in the web.xml. Using this particular method of Spring instantiation means that the Spring application context loaded is actually a WebApplicationContext with access to the ServletContext. In my web application it is very useful to be able to check the status of my job via a variable stored in the ServletContext. Armed with the above technique it is now quite easy to access the WebApplicationContext and therefore the underlying ServletContext.


public class MyJob implements Job {

private static final String APPLICATION_CONTEXT_KEY = "applicationContext";

public void execute(JobExecutionContext context) throws JobExecutionException {
ApplicationContext appCtx = getApplicationContext(context);

WebApplicationContext webCtx = null;
ServletContext srvCtx = null;
if (appCtx instanceof WebApplicationContext){
webCtx = (WebApplicationContext) appCtx;
srvCtx = webCtx.getServletContext();
srvCtx.setAttribute("foo", "bar");
}
// place rest of your Job code here
System.out.println("EXECUTING QUARTZ JOB");
}

private ApplicationContext getApplicationContext(JobExecutionContext context )
throws Exception {
... shown previously ...
}

}

I hope these features are useful to people. I sometimes worry that Spring hides it's beauty under a bushel a little too much but I suppose the problem is Spring provides such an embarrassment of riches it is impossible to highlight everything useful.

Incidentally, in this post I have been experimenting with Google's prettify.js syntax highlighter. Looks good to me, cheers Google!