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!

31 comments:

ismjml said...

Mark, Spring also provides a FactoryBean implementation that allows you to delegate to a Spring-managed bean from the JobDetail. For example:

























Then, simply configure the 'jobDetail' within the trigger as usual.


Note: Comment imported. Original by Mark Fisher website: http://blog.interface21.com/main/author/markf at 2007-06-21 00:39

ismjml said...

Damn, I did this using nasty singletons. Nice tip.
Note: Comment imported. Original by Anonymous at 2007-06-21 10:46

ismjml said...

hello,



I don't understand how you start the scheduler...
Note: Comment imported. Original by largo at 2007-06-26 15:58

ismjml said...

The SchedulerFactoryBean starts the scheduler. The SchedulerFactoryBean is an InitializingBean which means that once the Spring applicationContext is initialized the scheduler will start (this is autowiring!). In a web application you can use the ContextLoaderListener to initialize the applicationContext (I tend to use the terms "Spring container", BeanFactory and applicationContext to refer to the same thing although they are not technically exactly the same!). Alternatively, you can use something like:





Resource resource = new FileSystemResource("applicationContext.xml");

BeanFactory factory = new XmlBeanFactory(resource);





HTH



Mark
Note: Comment imported. Original by Mark McLaren website: http://content.mark-mclaren.info/ at 2007-06-27 08:05

ismjml said...

thank you for your answer.



But now the scheduler is started, how do you stop it ?
Note: Comment imported. Original by largo at 2007-06-27 09:42

ismjml said...

Normally, the scheduled job will stop when your applicationContext is unloaded.





If you want it to stop programmatically you can also call the stop() method on the

SchedulerFactoryBean. e.g. if your SchedulerFactoryBean had an id of schedulerFactoryBean you would do something like this:





SchedulerFactoryBean scheduler = (SchedulerFactoryBean) factory.getBean("schedulerFactoryBean");

scheduler.stop();

// Alternatively

// scheduler.destroy();




Note: Comment imported. Original by Mark McLaren website: http://content.mark-mclaren.info/ at 2007-06-27 14:03

ismjml said...

this is very nice tutorial, I was thinking

accessing ApplicationContect within the job class, actually it's very nasty. Thanks for sharing this
Note: Comment imported. Original by Anonymous at 2007-07-09 19:56

ismjml said...

Thanks for this article, I couldn't work out why my application was just stopping - I used the MethodInvokingJobDetailFactoryBean suggested in the first comment and it worked straight out.
Note: Comment imported. Original by Paul at 2007-07-10 11:03

ismjml said...

Hey,

you can simplify your config by extending

your Job bean from Springs QuartzJobBean

class. It allows you to wire up other context beans in the jobDataMap and automatically populates matching properties (setter methods required).

It saves you from the hassle of accessing the appContext manually.



greetz,

Jan










Note: Comment imported. Original by Anonymous at 2007-08-27 11:23

ismjml said...

Thanks Mark, useful article.



Is there a way we can call Job2 after successful completion of Job1 by using spring framework (Job chaining).


Note: Comment imported. Original by Dev at 2007-11-16 20:25

ismjml said...

Nice article
Note: Comment imported. Original by Anonymous at 2007-11-16 20:55

ismjml said...

Hi Dev,



Standard disclaimer: I have never done this before!. Quartz job chaining looks very possible judging by the documentation.





You should be able to pass a list of TriggerListeners or JobListeners into the SchedulerFactoryBean and use these to invoke new jobs.





Since this job code will be outside of the Spring Framework control you have take some extra care and produce thread safe code (e.g. avoid SimpleDateFormat and such!).



Good luck with it.
Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2007-11-17 12:16

ismjml said...

Thanks Mark, I registed JobListener with SchedularFactory and it worked.
Note: Comment imported. Original by devadossp at 2007-11-19 19:08

ismjml said...

Hi Mark!

While my web app is running, i stopped the scheduler, then i restart it and the getApplicationContext function return null. How can i get the application context again?
Note: Comment imported. Original by duc at 2007-11-26 04:42

ismjml said...

Hi,

Can you tell me how you add it.




Note: Comment imported. Original by Anonymous at 2007-11-30 13:35

ismjml said...

How can we stop the scheduler (myScheduler) and kill all the jobs?




Note: Comment imported. Original by Anonymous at 2008-01-08 20:55

ismjml said...

My spring application is a web application that is deployed on tomcat. In the quartz job, I would like to be able to determine the servername/host, port and the contextPath for the application. Is this possible?
Note: Comment imported. Original by Anonymous at 2008-01-24 17:38

ismjml said...

I do not think it is possible to derive this information from the ServletContext. Server name, server port and context path are properties of the HTTP servlet request.





e.g. I can access my blog using:





http://content.mark-mclaren.info/ and http://localhost/blog/ and the given values for server and port would change! These values are request specific.


Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2008-01-25 14:59

ismjml said...

I'm guessing something like this would work:





((Scheduler) factory.getBean("myScheduler")).shutdown();


Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2008-01-25 17:33

ismjml said...

For almost a day i fought with this:



java.lang.IllegalStateException: SchedulerFactoryBean needs to be set up in an ApplicationContext to be able to handle an 'applicationContextSchedulerContextKey'



For others facing the same error check that you call ctx.refresh() like in:

GenericApplicationContext ctx = new GenericApplicationContext();



XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);

xmlReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);

xmlReader.loadBeanDefinitions(new FileSystemResource(fileName));

ctx.refresh();


Note: Comment imported. Original by Bogdan O at 2008-01-29 13:36

ismjml said...

The "applicationContext" does not have bean definition anywhere. Is it a name convention that Spring knows what to inject into?
Note: Comment imported. Original by Anonymous at 2008-03-05 15:16

ismjml said...

See your web.xml file...



webAppRootKey

yourKey


Note: Comment imported. Original by Regis Melo website: http://www.softsite.com.br/regismelo at 2008-05-20 14:21

ismjml said...

What are libraries necessary for intregate quartz in your aplication with spring?
Note: Comment imported. Original by ramonchu at 2008-11-21 11:54

ismjml said...

What about accessing spring beans from Quartz Job that has been persisted in JobStore other than RAMJobStore, e.g. JobStoreTX, and then got fired after application was restarted? What I currently get is that Quartz and application hangs when trying to fire jobs loaded from jobstore.





These jobs were added using misfire instruction SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW so ones that were misfired while application was down are attempted to be fired as soon as application and quartz are started.





Who is responsible to wire new application context to the Job? Quartz I guess, but I'm not sure if that is happening/implemented. If spring application context or beans can/should not be in job that is persisted (to avoid Quartz hanging application startup), is there a way to access web application context started by the server e.g. using some thread local variable?
Note: Comment imported. Original by Stevo at 2009-01-17 21:59

ismjml said...

Hello...



I have a problem with the WebApplicationContext. Get the ApplicationContext but in the if (appCtx instanceof WebApplicationContext) not see the appCtx as instance of the context of a apllication web.



In the web.xml I have:



webAppRootKey

applicationContext





In the code of job:

ApplicationContext appCtx = getApplicationContext(context);



DatosMaestrosBf datosMaestrosBf = (DatosMaestrosBf) appCtx.getBean("datosMaestrosBf");

//Carga las regiones

List listaRegiones = datosMaestrosBf.consultarRegiones(transaccionId,0,-1,0);



WebApplicationContext webCtx = null;

ServletContext srvCtx = null;

if (appCtx instanceof WebApplicationContext){

webCtx = (WebApplicationContext) appCtx;

srvCtx = webCtx.getServletContext();

// srvCtx.setAttribute("foo", "bar");







srvCtx.setAttribute(Constantes.LISTA_REGIONES, listaRegiones);



}



thanks
Note: Comment imported. Original by bsdsilva at 2009-04-22 22:59

ismjml said...

I think the instance of ApplicationContext inside a web application should be a WebApplicationContext. A WebApplicationContext should be created by your "org.springframework.web.context.ContextLoaderListener" also inside web.xml. Alternatively, you could try using a RequestContextHolder to access your underlying request (which requires that you use a RequestContextFilter in your web.xml).
Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2009-04-27 08:36

ismjml said...

I am using weblogic 10.3 and Spring 1.2.6 with quartz 1.6.5 jars.



In my weblogic server I have two web applications deployed. Each web app has got a spring config file which defined the trigger and the jobs which reside in the respective war files. When I deploy the any one web application the quartz job runs fine and there is no error in the quartz tables. But the moment I deploy the secong application also I get a ClassNotFound Exception for of the jobs defined in web applications. Then I have two enteries in the QRTZ_SCHEDULER_STATE with generated Instance names.

I think what is happening is the quartz job is running within a particular web application so does not find the job which resides in a different web application. ?



I wanted to know if it is possible to have two SchedulerFactoryBean in two different web applications but deployed on the same weblogic server and talking to same quartz schema.
Note: Comment imported. Original by Manoj at 2009-10-15 14:04

ismjml said...

Hi Are you able to stop the scheduled batch by using



I am getting issue with that



StdScheduler scheduler = (StdScheduler) factory.getBean("schedulerFactoryBean");

scheduler.shutdown();





When i use org.springframework.scheduling.quartz.SchedulerFactoryBean



SchedulerFactoryBean scheduler =(factory.getBean("schedulerFactoryBean"))factory.getBean("schedulerFactoryBean");



I got type cast exception any thoughts on this
Note: Comment imported. Original by Anonymous at 2009-12-02 17:21

Jitendra Singh said...

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/

YLM said...

Thank you Mark. It helped me lots. I make it by code, because the job is programed by the end user. Here´s my code.

ApplicationContext applicationContext =
WebApplicationContextUtils.getWebApplicationContext(ServletActionContext.getServletContext());

...

Scheduler scheduler;
try {
scheduler = new StdSchedulerFactory().getScheduler();
scheduler.getContext().put("applicationContext", applicationContext);
//Listener attached to jobKey
scheduler.getListenerManager().addJobListener(
new TareasListener(), KeyMatcher.keyEquals(jobKey));
scheduler.start();
scheduler.scheduleJob(job, trigger);
} catch (SchedulerException ex) {
Logger.getLogger(JobLauncherAction.class.getName()).log(Level.SEVERE, null, ex);
}

IN THE JOB GET THE CONTEXT AS YOU DID

private ApplicationContext getApplicationContext(JobExecutionContext context)
throws Exception {
ApplicationContext appCtx = null;
appCtx = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
if (appCtx == null) {
log.error("%s", "No application context available in scheduler context for key \"applicationContext\"");
}
return appCtx;
}

YLM said...
This comment has been removed by the author.