Quartz定时任务注入Spring服务

前两天完成了Quartz和Spring的整合,见《Spring与Quartz整合实践》,但是当时给出的例子中并没有使用Spring的Service来实现一些功能,今天调试的时候问题就出现了。Quartz中的Job居然无法识别Spring的Service Bean对象?!

在各大论坛找了很久,发现一点问题了。原因是Job类是由Quartz的Job工厂来加载的,而在Spring中,这个Job工厂加载的类和Spring工厂加载的类根本就不是同一类,因此Job里面的注入是失败的。让我们看一下源码。

源码分析

先看看org.springframework.scheduling.quartz.SchedulerFactoryBean这个类的jobFactory属性,我们看一下它被设值的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void afterPropertiesSet() throws Exception {
// 前面的代码太长了,省略掉了

// Get Scheduler instance from SchedulerFactory.
try {
this.scheduler = createScheduler(schedulerFactory, this.schedulerName);
populateSchedulerContext();

if (!this.jobFactorySet && !(this.scheduler instanceof RemoteScheduler)) {
// Use AdaptableJobFactory as default for a local Scheduler, unless when
// explicitly given a null value through the "jobFactory" bean property.
this.jobFactory = new AdaptableJobFactory();
}
if (this.jobFactory != null) {
if (this.jobFactory instanceof SchedulerContextAware) {
((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext());
}
this.scheduler.setJobFactory(this.jobFactory);
}
}

// 后面的代码也省略掉了
}

我们可以看到中间这一句注释Use AdaptableJobFactory as default for a local Scheduler, unless when explicitly given a null value through the "jobFactory" bean property.意思就是这个SchedulerFactoryBean会默认将AdaptableJobFactory作为它的Job工厂。除非它的jobFactory属性被显式地赋上了一个非空的值。而默认情况下,我们在Spring的XML配置中都不会指定这个属性,所以默认就是这个AdaptableJobFactory了。那我们来看一看这个AdaptableJobFactory具体做了什么工作?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class AdaptableJobFactory implements JobFactory {

@Override
public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
try {
Object jobObject = createJobInstance(bundle);
return adaptJob(jobObject);
}
catch (Exception ex) {
throw new SchedulerException("Job instantiation failed", ex);
}
}

/**
* Create an instance of the specified job class.
* <p>Can be overridden to post-process the job instance.
* @param bundle the TriggerFiredBundle from which the JobDetail
* and other info relating to the trigger firing can be obtained
* @return the job instance
* @throws Exception if job instantiation failed
*/
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
return bundle.getJobDetail().getJobClass().newInstance();
}

/**
* Adapt the given job object to the Quartz Job interface.
* <p>The default implementation supports straight Quartz Jobs
* as well as Runnables, which get wrapped in a DelegatingJob.
* @param jobObject the original instance of the specified job class
* @return the adapted Quartz Job instance
* @throws Exception if the given job could not be adapted
* @see DelegatingJob
*/
protected Job adaptJob(Object jobObject) throws Exception {
if (jobObject instanceof Job) {
return (Job) jobObject;
}
else if (jobObject instanceof Runnable) {
return new DelegatingJob((Runnable) jobObject);
}
else {
throw new IllegalArgumentException("Unable to execute job class [" + jobObject.getClass().getName() +
"]: only [org.quartz.Job] and [java.lang.Runnable] supported.");
}
}

}

这个类非常简单,我们重点看一下第二个方法createJobInstance,这个方法里面的工作就是通过反射的方式将我们的Job进行了实例化。但是这个Job并没有被加入到Spring容器中去,所以问题就找到了。现在我们需要做的事情非常简单,就是重载这个方法,将反射实例化的Job对象加入到Spring容器中,而能够做这个工作的是org.springframework.beans.factory.config.AutowireCapableBeanFactory。就是能够让我们平常经常使用@Autowired注解的那个Bean工厂。

问题解决

这里我们需要继承这个org.springframework.scheduling.quartz.SpringBeanJobFactory,代码如下(我定义的类取名为AcsJobFactory.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.qinjiangbo.scheduler;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
* @date: 09/12/2017 8:43 PM
* @author: [email protected]
* @description:
*/
public class AcsJobFactory extends SpringBeanJobFactory {

@Autowired
private AutowireCapableBeanFactory autowireCapableBeanFactory;

@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
// 调用父类方法
Object jobInstance = super.createJobInstance(bundle);
// 注入对象
autowireCapableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}

这样Job工厂产生的类就直接进入Spring容器啦,我们就能够直接在Job中获取到Spring容器中的Service的Bean对象。修改Spring的XML配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 全局定时任务调度器 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="proxyDumpJob"/>
<ref bean="blockDumpJob"/>
<ref bean="blockDeleteJob"/>
</list>
</property>
<property name="triggers">
<list>
<ref bean="proxyDumpTrigger"/>
<ref bean="blockDumpTrigger"/>
<ref bean="blockDeleteTrigger"/>
</list>
</property>
<!-- 这里需要指定jobFactory属性的值 -->
<property name="jobFactory" ref="acsJobFactory"/>
</bean>

总结

今天碰到这个问题也还是挺新鲜的,所以比较感兴趣,没想到发现了这个原因,收获还是很大的。关于Spring容器是如何加载的,Quartz是如何创造Job的,都需要在后面进一步地研究和深挖。能从源码上入手解决某一个问题的时候,才是对这个问题比较深入地理解。

分享到