Accessing Spring beans from Quartz jobs

来源:互联网 发布:谭浩强c语言第四版错误 编辑:程序博客网 时间:2024/05/16 14:58

 

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!

Tags : quartz spring

Comments
 
Re: Accessing Spring beans from Quartz jobs
Mark, Spring also provides a FactoryBean implementation that allows you to delegate to a Spring-managed bean from the JobDetail. For example:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">

<property name="targetObject" ref="foo"/>

<property name="targetMethod" value="bar"/>

</bean>



<bean id="foo" class="example.Foo">

<!-- dependency inject properties and/or constructor args -->

</bean>

Then, simply configure the 'jobDetail' within the trigger as usual.
Re: Accessing Spring beans from Quartz jobs
Damn, I did this using nasty singletons. Nice tip.
Re: Accessing Spring beans from Quartz jobs
hello, I don't understand how you start the scheduler...
Re: Accessing Spring beans from Quartz jobs

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
Re: Accessing Spring beans from Quartz jobs
thank you for your answer. But now the scheduler is started, how do you stop it ?
Re: Accessing Spring beans from Quartz jobs

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();

Re: Accessing Spring beans from Quartz jobs
this is very nice tutorial, I was thinking accessing ApplicationContect within the job class, actually it's very nasty. Thanks for sharing this
Re: Accessing Spring beans from Quartz jobs
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
Re: Accessing Spring beans from Quartz jobs
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.
Re: Accessing Spring beans from Quartz jobs
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
Re: Accessing Spring beans from Quartz jobs
Nice article
Re: Accessing Spring beans from Quartz jobs
Thanks Mark, useful article. Is there a way we can call Job2 after successful completion of Job1 by using spring framework (Job chaining).
Re: Accessing Spring beans from Quartz jobs
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.
Re: Accessing Spring beans from Quartz jobs
Thanks Mark, I registed JobListener with SchedularFactory and it worked.
Re: Accessing Spring beans from Quartz jobs
Hi, Can you tell me how you add it.
Re: Accessing Spring beans from Quartz jobs
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?
Re: Accessing Spring beans from Quartz jobs
 

<bean id="myScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

<propertyy name="triggers">

<list>

<ref bean="simpleTrigger"/>

</list>

</property>

</bean>



How can we stop the scheduler (myScheduler) and kill all the jobs?
Re: Accessing Spring beans from Quartz jobs

I'm guessing something like this would work:

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

Re: Accessing Spring beans from Quartz jobs
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?
Re: Accessing Spring beans from Quartz jobs

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://cse-mjmcl.cse.bris.ac.uk/blog/ and http://localhost/blog/ and the given values for server and port would change! These values are request specific.

java.lang.IllegalStateException: SchedulerFactoryBean needs to be set up in an ApplicationContext to be able to handle an 'applicationContextSchedulerContextKey'
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();

Re: Accessing Spring beans from Quartz jobs
The "applicationContext" does not have bean definition anywhere. Is it a name convention that Spring knows what to inject into?
Re: Accessing Spring beans from Quartz jobs
See your web.xml file... <context-param> <param-name>webAppRootKey</param-name> <param-value>yourKey</param-value> </context-param>
Re: Accessing Spring beans from Quartz jobs
What are libraries necessary for intregate quartz in your aplication with spring?
Re: Accessing Spring beans from Quartz jobs
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?
Re: Accessing Spring beans from Quartz jobs
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: <context-param> <param-name>webAppRootKey</param-name> <param-value>applicationContext</param-value> </context-param> In the code of job: ApplicationContext appCtx = getApplicationContext(context); DatosMaestrosBf datosMaestrosBf = (DatosMaestrosBf) appCtx.getBean("datosMaestrosBf"); //Carga las regiones List<RegionTo> 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
Re: Accessing Spring beans from Quartz jobs
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).
problem in running multiple quartz scheduler inside onw weblogic server
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.
原创粉丝点击