在 Spring 中将 bean 引用注入到 Quartz 作业中?

我设法在 Spring 中使用 JobStoreTX 持久存储来配置和调度 Quartz 作业。我不使用 Spring 的 Quartz 作业,因为我需要在运行时动态调度它们,我发现所有将 Spring 与 Quartz 集成的例子都是在 Spring 配置文件中硬编码调度的... ... 不管怎样,下面是我调度作业的方法:

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

EMailJob 是一个简单的作业,它使用 Spring 的 JavaMailSenderImpl 类发送电子邮件。

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


}

问题是,我需要在 EMailJob 类中获得对这个类的一个实例(JavaMailSenderImpl)的引用。当我像这样注射的时候:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

它没有被注入-引用是 NULL。我假设这种情况的发生是因为不是 Spring 实例化了 emailJob 类,而是 Quartz,而 Quartz 对依赖注入一无所知... ..。

那么,有没有什么方法可以强制注射呢?

谢谢!

更新1: @ Aaron: 下面是来自启动的堆栈跟踪的相关部分,它显示了 EMailJob 被实例化了两次:

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 ...

谢谢!

更新 # 2:@Ryan:

我尝试使用 SpringBeanJobFactory 如下:

    <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>

我已经修改了我的主类从这个工厂获得调度器,而不是石英’:

    @PostConstruct
public void initNotificationScheduler() {
try {
//sf = new StdSchedulerFactory("spring/quartz.properties");
//scheduler = sf.getScheduler();


scheduler = schedulerFactoryBean.getScheduler();
scheduler.start();
....

但是当我运行 app-get 错误时,请参见下面。下面是 Spring 启动时的堆栈跟踪。看起来计划程序本身创建得不错,但是当它试图实例化我的 Email Job 时出现了错误:

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)

谢谢!

112488 次浏览

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.

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.

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

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

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

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

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.

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

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

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.

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.

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
}


}

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 project
  • AutowiringSpringBeanJobFactory 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!

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

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


}