在上一篇Spring集成Quartz中,我们介绍了如何将Quartz框架集成到spring环境中来,在PrintJob示例中,我们仅仅是简单的做了Sleep操作。实际上,我们经常需要在具体的job中调用我们的Service服务,而服务通常是作为SpringBean方式托管的。
一种方案是通过ContextUtil每次去getBean,例如:
EchoService echoService = (EchoService)ContextUtil.getBean("echoService");
这些写实际上是有些别扭的,每次调用都要写这么一行代码,有时候getBean还可能导致NPE。实际上我们想要的是这样的效果:
public class PrintJob implements InterruptableJob {
@Autowired
EchoService echoService;
......
}
能够进行自动注入,这样我们就能直接使用了。
我们先回过头来,spring集成quartz是通过spring-context-support包下的SchedulerFactoryBean实现的,配置如下:
<!-- quartz scheduler配置 -->
<bean name="quartzScheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:quartz.properties" />
<property name="overwriteExistingJobs" value="true" />
<property name="autoStartup" value="false" />
<property name="exposeSchedulerInRepository" value="true" />
</bean>
在该工厂类的初始化中,将创建Job的工厂类设置成了AdaptableJobFactory(在quartz框架内部默认是一个PropertySettingJobFactory的类):
@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);
}
}
......
}
我们来看看AdaptableJobFactory是如何创建Job的:
package org.springframework.scheduling.quartz;
import org.quartz.Job;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
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);
}
}
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
return bundle.getJobDetail().getJobClass().newInstance();
}
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.");
}
}
}
AdaptableJobFactory做了两件事:
1-根据JobClass创建实例(非常简单粗暴的创建实例)
2-对该实例进行代理(如果该实例是Runnable接口的实现类,则返回一个静态代理类)
那么如果我们想要自动注入,从createJobInstance着手就可以了。spring提供了一个AutowireCapableBeanFactory的类,可以对spring外部创建的实例进行属性注入。我们先来改造一个JobFactory。
/**
* jobFactory,让Job可以进行spring属性自动注入
* Created by gameloft9 on 2019/4/9.
*/
@Component(value = "springJobFactory")
public class SpringJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//创建job实例
Object jobInstance = super.createJobInstance(bundle);
//进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
然后将其作为SchedulerFactoryBean的默认Job工厂类:
<!-- quartz scheduler配置 -->
<bean name="quartzScheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jobFactory" ref="springJobFactory"/>
<property name="configLocation" value="classpath:quartz.properties" />
<property name="overwriteExistingJobs" value="true" />
<property name="autoStartup" value="false" />
<property name="exposeSchedulerInRepository" value="true" />
</bean>
这样就大功告成了。
下面我们来测试一下,先创建一个Service:
/**
* 模拟spring bean
* Created by gameloft9 on 2019/4/9.
*/
@Component
@Slf4j
@Data
public class EchoService {
public void echo(String msg){
log.info("echo {}",msg);
}
}
然后在我们的PrintJob中通过@Autowired自动注入:
package com.gameloft9.demo.jobs;
import com.gameloft9.demo.service.EchoService;
import com.gameloft9.demo.util.ContextUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
/**
* job示例
* Created by gameloft9 on 2019/4/8.
*/
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
@Data
public class PrintJob implements InterruptableJob {
@Autowired
EchoService echoService;
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
String countStr = context.getJobDetail().getJobDataMap().getString("count");
long count = 0;
if (countStr != null){
count = Long.parseLong(countStr);
}
context.getJobDetail().getJobDataMap().put("count", "" + (count + 1));
// 模拟任务执行
echoService.echo("执行任务中...");
log.info("任务执行成功,累计执行次数:{}",count);
} catch (Exception e) {
log.error("", e);
} finally {
}
}
public void interrupt() throws UnableToInterruptJobException {
// do nothing
}
}