inject bean reference into a Quartz job in Spring?
I managed to configure and schedule a Quartz job using JobStoreTX persistent store in Spring. I do not use Spring's Quartz jobs, because I need to schedule them dynamically, at run time, and all examples of integrating Spring with Quartz that i found were hard-coding the shcedules in the Spring config files... Anyway, here is how I schedule the job:
JobDetail emailJob = JobBuilder.newJob(EMailJob.class)
.withIdentity("someJobKey", "immediateEmailsGroup")
.storeDurably()
.build();
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("someTriggerKey", "immediateEmailsGroup")
.startAt(fireTime)
.build();
// pass initialization parameters into the job
emailJob.getJobDataMap().put(NotificationConstants.MESSAGE_PARAMETERS_KEY, messageParameters);
emailJob.getJobDataMap().put(NotificationConstants.RECIPIENT_KEY, recipient);
if (!scheduler.checkExists(jobKey) && scheduler.getTrigger(triggerKey) != null) {
// schedule the job to run
Date scheduleTime1 = scheduler.scheduleJob(emailJob, trigger);
}
The EMailJob is a simple job that is sending e-mail using the Spring's JavaMailSenderImpl class.
public class EMailJob implements Job {
@Autowired
private JavaMailSenderImpl mailSenderImpl;
public EMailJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
....
try {
mailSenderImpl.send(mimeMessage);
} catch (MessagingException e) {
....
throw new JobExecutionException("EMailJob failed: " + jobKey.getName(), e);
}
logger.info("EMailJob finished OK");
}
The problem is that I need to get a reference to an instance of this class (JavaMailSenderImpl) in my EMailJob class. When I try to inject it like this:
@Autowired
private JavaMailSenderImpl mailSenderImpl;
it is not injected - the reference is NULL. I'm assuming this is happening because it is not Spring who instantiates the EMailJob class, but Quartz, and Quartz does not know anything about dependency injection...
So, is there some way to force this injection to happen?
thanks!
Update 1: @Aaron: here is a relevant part of the stacktrace from the startup, which is showing the the EMailJob was instantiated twice:
2011-08-15 14:16:38,687 [main] INFO org.springframework.context.support.GenericApplicationContext - Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler#0' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2011-08-15 14:16:38,734 [main] INFO org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1328c7a: defining beans [...]; root of factory hierarchy
2011-08-15 14:16:39,734 [main] INFO com.cambridgedata.notifications.EMailJob - EMailJob() - initializing ...
2011-08-15 14:16:39,937 [main] INFO org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Validated configuration attributes
2011-08-15 14:16:40,078 [main] INFO org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Validated configuration attributes
2011-08-15 14:16:40,296 [main] INFO org.springframework.jdbc.datasource.init.ResourceDatabasePopulator - Executing SQL script from class path resource ...
2011-08-15 14:17:14,031 [main] INFO com.mchange.v2.log.MLog - MLog clients using log4j logging.
2011-08-15 14:17:14,109 [main] INFO com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2011-08-15 14:17:14,171 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2011-08-15 14:17:14,171 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.0.1 created.
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2011-08-15 14:17:14,187 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.0.1) 'NotificationsScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'NotificationsScheduler' initialized from the specified file : 'spring/quartz.properties' from the class resource path.
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.0.1
2011-08-15 14:17:14,234 [main] INFO com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 2sajb28h1lcabf28k3nr1|13af084, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 2sajb28h1lcabf28k3nr1|13af084, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/2010rewrite2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 0 from dual, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2011-08-15 14:17:14,312 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2011-08-15 14:17:14,328 [main] INFO org.quartz.core.QuartzScheduler - Scheduler NotificationsScheduler_$_NON_CLUSTERED started.
2011-08-15 14:17:14,515 [NotificationsScheduler_QuartzSchedulerThread] INFO com.cambridgedata.notifications.EMailJob - EMailJob() - initializing ...
thanks!
Update #2: @Ryan:
I tried to use the SpringBeanJobFactory as following:
<bean id="jobFactoryBean" class="org.springframework.scheduling.quartz.SpringBeanJobFactory">
</bean>
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:spring/quartz.properties"/>
<property name="jobFactory" ref="jobFactoryBean"/>
</bean>
And I have modified my main class to get Scheduler from this factory, instead of Quartz':
@PostConstruct
public void initNotificationScheduler() {
try {
//sf = new StdSchedulerFactory("spring/quartz.properties");
//scheduler = sf.getScheduler();
scheduler = schedulerFactoryBean.getScheduler();
scheduler.start();
....
But when I run the app - get errors, see below. Here is the stacktrace from Spring startup . Seems like the Scheduler itself is created fine, but the error comes when it is trying to instantiate my EMailJob:
2011-08-15 21:49:42,968 [main] INFO org.springframework.scheduling.quartz.SchedulerFactoryBean - Loading Quartz config from [class path resource [spring/quartz.properties]]
2011-08-15 21:49:43,031 [main] INFO com.mchange.v2.log.MLog - MLog clients using log4j logging.
2011-08-15 21:49:43,109 [main] INFO com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.0.1 created.
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.0.1) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.0.1
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@566633
2011-08-15 21:49:43,265 [main] INFO com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hge13f8h1lsg7py1rg0iu0|1956391, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge13f8h1lsg7py1rg0iu0|1956391, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/2010rewrite2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 0 from dual, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2011-08-15 21:49:43,343 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2011-08-15 21:49:43,359 [main] INFO org.quartz.core.QuartzScheduler - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2011-08-15 21:49:43,562 [schedulerFactoryBean_QuartzSchedulerThread] ERROR org.quartz.core.ErrorLogger - An error occured instantiating job to be executed. job= 'immediateEmailsGroup.DEFAULT.jobFor_1000new1'
org.quartz.SchedulerException: Problem instantiating class 'com.cambridgedata.notifications.EMailJob' - [See nested exception: java.lang.AbstractMethodError: org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;]
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:141)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:381)
Caused by: java.lang.AbstractMethodError: org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:134)
开发者_JAVA技巧thanks!
You can use this SpringBeanJobFactory
to automatically autowire quartz objects using spring:
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
Then, attach it to your SchedulerBean
(in this case, with Java-config):
@Bean
public SchedulerFactoryBean quartzScheduler() {
SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
...
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
quartzScheduler.setJobFactory(jobFactory);
...
return quartzScheduler;
}
Working for me, using spring-3.2.1 and quartz-2.1.6.
Check out the complete gist here.
I found the solution in this blog post
I just put SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
as first line of my Job.execute(JobExecutionContext context)
method.
Same problem has been resolved in LINK:
I could found other option from 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>
Then using below code in your job class you can get the applicationContext and get whatever bean you want.
appCtx = (ApplicationContext)context.getScheduler().getContext().get("applicationContextSchedulerContextKey");
Hope it helps. You can get more information from Mark Mclaren'sBlog
You're right in your assumption about Spring vs. Quartz instantiating the class. However, Spring provides some classes that let you do some primitive dependency injection in Quartz. Check out SchedulerFactoryBean.setJobFactory() along with the SpringBeanJobFactory. Essentially, by using the SpringBeanJobFactory, you enable dependency injection on all Job properties, but only for values that are in the Quartz scheduler context or the job data map. I don't know what all DI styles it supports (constructor, annotation, setter...) but I do know it supports setter injection.
for all who will try this in the future.
org.springframework.scheduling.quartz.JobDetailBean supplies map of objects and those objects may be spring beans.
define smth like
<bean name="myJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass"
value="my.cool.class.myCoolJob" />
<property name="jobDataAsMap">
<map>
<entry key="myBean" value-ref="myBean" />
</map>
</property>
</bean>
and then, inside
public void executeInternal(JobExecutionContext context)
call myBean = (myBean) context.getMergedJobDataMap().get("myBean");
and you all set.
I know, it looks ugly, but as a workaround it works
ApplicationContext springContext =
WebApplicationContextUtils.getWebApplicationContext(ContextLoaderListener .getCurrentWebApplicationContext().getServletContext());
Bean bean = (Bean) springContext.getBean("beanName");
bean.method();
Thanks, Rippon! I have finally got this working too, after many struggles, and my solution is very close to what you suggested! The key was to make my own Job to extend QuartzJobBean, and to use the schedulerContextAsMap.
I did get away without specifying the applicationContextSchedulerContextKey property - it worked without it for me.
For the benefit of others, here is the final configuration that has worked for me:
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:spring/quartz.properties"/>
<property name="jobFactory">
<bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory" />
</property>
<property name="schedulerContextAsMap">
<map>
<entry key="mailService" value-ref="mailService" />
</map>
</property>
</bean>
<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="repeatCount" value="0" />
</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="com.cambridgedata.notifications.EMailJob" />
<property name="volatility" value="false" />
<property name="durability" value="false" />
<property name="requestsRecovery" value="true" />
</bean>
<bean id="notificationScheduler" class="com.cambridgedata.notifications.NotificationScheduler">
<constructor-arg ref="quartzScheduler" />
<constructor-arg ref="jobDetailFactory" />
<constructor-arg ref="jobTriggerFactory" />
</bean>
Notice that the 'mailService" bean is my own service bean, managed by Spring. I was able to access it in my Job as following:
public void executeInternal(JobExecutionContext context)
throws JobExecutionException {
logger.info("EMailJob started ...");
....
SchedulerContext schedulerContext = null;
try {
schedulerContext = context.getScheduler().getContext();
} catch (SchedulerException e1) {
e1.printStackTrace();
}
MailService mailService = (MailService)schedulerContext.get("mailService");
....
And this configuration also allowed me to dynamically scheduler jobs, by using factories to get Triggers and JobDetails and setting required parameters on them programmatically:
public NotificationScheduler(final Scheduler scheduler,
final ObjectFactory<JobDetail> jobDetailFactory,
final ObjectFactory<SimpleTrigger> jobTriggerFactory) {
this.scheduler = scheduler;
this.jobDetailFactory = jobDetailFactory;
this.jobTriggerFactory = jobTriggerFactory;
...
// create a trigger
SimpleTrigger trigger = jobTriggerFactory.getObject();
trigger.setRepeatInterval(0L);
trigger.setStartTime(new Date());
// create job details
JobDetail emailJob = jobDetailFactory.getObject();
emailJob.setName("new name");
emailJob.setGroup("immediateEmailsGroup");
...
Thanks a lot again to everybody who helped,
Marina
A simple solution is to set the spring bean in the Job Data Map and then retrieve the bean in the job class, for instance
// the class sets the configures the MyJob class
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
Date startTime = DateBuilder.nextGivenSecondDate(null, 15);
JobDetail job = newJob(MyJob.class).withIdentity("job1", "group1").build();
job.getJobDataMap().put("processDataDAO", processDataDAO);
`
// this is MyJob Class
ProcessDataDAO processDataDAO = (ProcessDataDAO) jec.getMergedJobDataMap().get("processDataDAO");
Here is what the code looks like with @Component:
Main class that schedules the job:
public class NotificationScheduler {
private SchedulerFactory sf;
private Scheduler scheduler;
@PostConstruct
public void initNotificationScheduler() {
try {
sf = new StdSchedulerFactory("spring/quartz.properties");
scheduler = sf.getScheduler();
scheduler.start();
// test out sending a notification at startup, prepare some parameters...
this.scheduleImmediateNotificationJob(messageParameters, recipients);
try {
// wait 20 seconds to show jobs
logger.info("sleeping...");
Thread.sleep(40L * 1000L);
logger.info("finished sleeping");
// executing...
} catch (Exception ignore) {
}
} catch (SchedulerException e) {
e.printStackTrace();
throw new RuntimeException("NotificationScheduler failed to retrieve a Scheduler instance: ", e);
}
}
public void scheduleImmediateNotificationJob(){
try {
JobKey jobKey = new JobKey("key");
Date fireTime = DateBuilder.futureDate(delayInSeconds, IntervalUnit.SECOND);
JobDetail emailJob = JobBuilder.newJob(EMailJob.class)
.withIdentity(jobKey.toString(), "immediateEmailsGroup")
.build();
TriggerKey triggerKey = new TriggerKey("triggerKey");
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity(triggerKey.toString(), "immediateEmailsGroup")
.startAt(fireTime)
.build();
// schedule the job to run
Date scheduleTime1 = scheduler.scheduleJob(emailJob, trigger);
} catch (SchedulerException e) {
logger.error("error scheduling job: " + e.getMessage(), e);
e.printStackTrace();
}
}
@PreDestroy
public void cleanup(){
sf = null;
try {
scheduler.shutdown();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
The EmailJob is the same as in my first posting except for the @Component annotation:
@Component
public class EMailJob implements Job {
@Autowired
private JavaMailSenderImpl mailSenderImpl;
... }
And the Spring's configuration file has:
...
<context:property-placeholder location="classpath:spring/*.properties" />
<context:spring-configured/>
<context:component-scan base-package="com.mybasepackage">
<context:exclude-filter expression="org.springframework.stereotype.Controller"
type="annotation" />
</context:component-scan>
<bean id="mailSenderImpl" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="${mail.host}"/>
<property name="port" value="${mail.port}"/>
...
</bean>
<bean id="notificationScheduler" class="com.mybasepackage.notifications.NotificationScheduler">
</bean>
Thanks for all the help!
Marina
This is a quite an old post which is still useful. All the solutions that proposes these two had little condition that not suite all:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
This assumes or requires it to be a spring - web based projectAutowiringSpringBeanJobFactory
based approach mentioned in previous answer is very helpful, but the answer is specific to those who don't use pure vanilla quartz api but rather Spring's wrapper for the quartz to do the same.
If you want to remain with pure Quartz implementation for scheduling(Quartz with Autowiring capabilities with Spring), I was able to do it as follows:
I was looking to do it quartz way as much as possible and thus little hack proves helpful.
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory{
private AutowireCapableBeanFactory beanFactory;
public AutowiringSpringBeanJobFactory(final ApplicationContext applicationContext){
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
beanFactory.initializeBean(job, job.getClass().getName());
return job;
}
}
@Configuration
public class SchedulerConfig {
@Autowired private ApplicationContext applicationContext;
@Bean
public AutowiringSpringBeanJobFactory getAutowiringSpringBeanJobFactory(){
return new AutowiringSpringBeanJobFactory(applicationContext);
}
}
private void initializeAndStartScheduler(final Properties quartzProperties)
throws SchedulerException {
//schedulerFactory.initialize(quartzProperties);
Scheduler quartzScheduler = schedulerFactory.getScheduler();
//Below one is the key here. Use the spring autowire capable job factory and inject here
quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);
quartzScheduler.start();
}
quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);
gives us an autowired job instance. Since AutowiringSpringBeanJobFactory
implicitly implements a JobFactory
, we now enabled an auto-wireable solution. Hope this helps!
A solution from Hary https://stackoverflow.com/a/37797575/4252764 works very well. It's simpler, doesn't need so many special factory beans, and support multiple triggers and jobs. Would just add that Quartz job can be made to be generic, with specific jobs implemented as regular Spring beans.
public interface BeanJob {
void executeBeanJob();
}
public class GenericJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getMergedJobDataMap();
((BeanJob)dataMap.get("beanJob")).executeBeanJob();
}
}
@Component
public class RealJob implements BeanJob {
private SomeService service;
@Autowired
public RealJob(SomeService service) {
this.service = service;
}
@Override
public void executeBeanJob() {
//do do job with service
}
}
A simple way to do it would be to just annotate the Quartz Jobs with @Component
annotation, and then Spring will do all the DI magic for you, as it is now recognized as a Spring bean. I had to do something similar for an AspectJ
aspect - it was not a Spring bean until I annotated it with the Spring @Component
stereotype.
The solution above is great but in my case the injection was not working. I needed to use autowireBeanProperties instead, probably due to the way my context is configured:
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
//beanFactory.autowireBean(job);
beanFactory.autowireBeanProperties(job, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
return job;
}
}
This is the right answer http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030. and will work for most of the folks. But if your web.xml does is not aware of all applicationContext.xml files, quartz job will not be able to invoke those beans. I had to do an extra layer to inject additional applicationContext files
public class MYSpringBeanJobFactory extends SpringBeanJobFactory
implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
try {
PathMatchingResourcePatternResolver pmrl = new PathMatchingResourcePatternResolver(context.getClassLoader());
Resource[] resources = new Resource[0];
GenericApplicationContext createdContext = null ;
resources = pmrl.getResources(
"classpath*:my-abc-integration-applicationContext.xml"
);
for (Resource r : resources) {
createdContext = new GenericApplicationContext(context);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(createdContext);
int i = reader.loadBeanDefinitions(r);
}
createdContext.refresh();//important else you will get exceptions.
beanFactory = createdContext.getAutowireCapableBeanFactory();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)
throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
You can add any number of context files you want your quartz to be aware of.
Simply extend your job from QuartzJobBean
public class MyJob extends QuartzJobBean {
@Autowired
private SomeBean someBean;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("Some bean is " + someBean.toString());
}
}
Make sure your
AutowiringSpringBeanJobFactory extends SpringBeanJobFactory
dependency is pulled from
"org.springframework:spring-context-support:4..."
and NOT from
"org.springframework:spring-support:2..."
It wanted me to use
@Override
public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler)
instead of
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)
so was failing to autowire job instance.
When you already use real AspectJ in your project, then you could annotate the job bean class with @Configurable
. Then Spring will inject into this class, even if it is constructed via new
I faced the similiar problem and came out from it with following approach:
<!-- Quartz Job -->
<bean name="JobA" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- <constructor-arg ref="dao.DAOFramework" /> -->
<property name="jobDataAsMap">
<map>
<entry key="daoBean" value-ref="dao.DAOFramework" />
</map>
</property>
<property name="jobClass" value="com.stratasync.jobs.JobA" />
<property name="durability" value="true"/>
</bean>
In above code I inject dao.DAOFramework bean into JobA bean and in inside ExecuteInternal method you can get injected bean like:
daoFramework = (DAOFramework)context.getMergedJobDataMap().get("daoBean");
I hope it helps! Thank you.
All those solutions above doesn't work for me with Spring 5 and Hibernate 5 and Quartz 2.2.3 when I want to call transactional methods!
I therefore implemented this solution which automatically starts the scheduler and triggers the jobs. I found a lot of that code at dzone. Because I don't need to create triggers and jobs dynamically I wanted the static triggers to be pre defined via Spring Configuration and only the jobs to be exposed as Spring Components.
My basic configuration look like this
@Configuration
public class QuartzConfiguration {
@Autowired
ApplicationContext applicationContext;
@Bean
public SchedulerFactoryBean scheduler(@Autowired JobFactory jobFactory) throws IOException {
SchedulerFactoryBean sfb = new SchedulerFactoryBean();
sfb.setOverwriteExistingJobs(true);
sfb.setAutoStartup(true);
sfb.setJobFactory(jobFactory);
Trigger[] triggers = new Trigger[] {
cronTriggerTest().getObject()
};
sfb.setTriggers(triggers);
return sfb;
}
@Bean
public JobFactory cronJobFactory() {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
@Bean
public CronTriggerFactoryBean cronTriggerTest() {
CronTriggerFactoryBean tfb = new CronTriggerFactoryBean();
tfb.setCronExpression("0 * * ? * * *");
JobDetail jobDetail = JobBuilder.newJob(CronTest.class)
.withIdentity("Testjob")
.build()
;
tfb.setJobDetail(jobDetail);
return tfb;
}
}
As you can see, you have the scheduler and a simple test trigger which is defined via a cron expression. You can obviously choose whatever scheduling expression you like. You then need the AutowiringSpringBeanJobFactory which goes like this
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
@Autowired
private ApplicationContext applicationContext;
private SchedulerContext schedulerContext;
@Override
public void setApplicationContext(final ApplicationContext context) {
this.applicationContext = context;
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
Job job = applicationContext.getBean(bundle.getJobDetail().getJobClass());
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
if (this.schedulerContext != null)
{
pvs.addPropertyValues(this.schedulerContext);
}
bw.setPropertyValues(pvs, true);
return job;
}
public void setSchedulerContext(SchedulerContext schedulerContext) {
this.schedulerContext = schedulerContext;
super.setSchedulerContext(schedulerContext);
}
}
In here you wire your normal application context and your job together. This is the important gap because normally Quartz starts it's worker threads which have no connection to your application context. That is the reason why you can't execute Transactional mehtods. The last thing missing is a job. It can look like that
@Component
public class CronTest implements Job {
@Autowired
private MyService s;
public CronTest() {
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
s.execute();
}
}
It's not a perfect solution because you an extra class only for calling your service method. But nevertheless it works.
Jdbc jobstore
If you are using jdbc jobstore Quartz uses a different classloader. That prevents all the workarounds for autowiring, since objects from spring will not be compatible at quartz side, because they originited from a different class loader.
To fix that, the default classloader has to be set in the quartz properties file like this:
org.quartz.scheduler.classLoadHelper.class=org.quartz.simpl.ThreadContextClassLoadHelper
As reference: https://github.com/quartz-scheduler/quartz/issues/221
精彩评论