如何在Spring Boot中使用Quartz
在网上看到关于Spring Boot整合Quartz的方式都看起来不是太好用,太复杂。
一般都会定义一个实现了QuartzJobBean 的任务类(这个类不会交给Spring管理)。然后给这个Job配置相应的JobDetail和Trigger。
具体操作如下:
1:定义一个任务
public class DateTimeJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//任务内容
}
2:配置相应的JobDetail和Trigger
@Configuration
public class QuartzConfig {
@Bean
public JobDetail printTimeJobDetail(){
return JobBuilder.newJob(DateTimeJob.class)//任务类
.withIdentity("DateTimeJob")//可以给该JobDetail起一个id
.usingJobData("msg", "Hello Quartz")//关联键值对
.build();
}
@Bean
public Trigger printTimeJobTrigger() {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(printTimeJobDetail())//关联上述的JobDetail
.withIdentity("quartzTaskService")//给Trigger起个名字
.withSchedule(cronScheduleBuilder)
.build();
}
}
上面是配置一个Quartz任务需要的操作,然后还需要为每个任务调用scheduler.scheduleJob(省略了Quartz其他的配置,只是给出了任务配置)。
可以看到几个问题:
1:创建的任务中只是一个空的方法,在实际使用过程中会有Spring容器中的Bean注入到任务中去,那它们能成功注入到里面去吗?因为我们的任务是交给JobBuilder去创建了,这个任务的Bean会不会存放到Spring容器不清楚。
2:每次添加一个任务都需要创建任务和对应的JobDetail和Trigger。在JobDetail和Trigger的配置基本都是一个套路配置,所以会有很多重复的代码。
如何优雅的在Spring Boot中整合Quartz呢?
首先解决第一个问题。创建的任务会被加入到Spring上下文中吗?
我们看看Quartz的任务执行过程
org.quartz.core.QuartzSchedulerThread#run
for (int i = 0; i < ((List) bndles).size(); ++i) {
TriggerFiredResult result = (TriggerFiredResult) ((List) bndles).get(i);
TriggerFiredBundle bndle = result.getTriggerFiredBundle();
Exception exception = result.getException();
if (exception instanceof RuntimeException) {
this.getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception);
this.qsRsrcs.getJobStore().releaseAcquiredTrigger((OperableTrigger) triggers.get(i));
} else if (bndle == null) {
this.qsRsrcs.getJobStore().releaseAcquiredTrigger((OperableTrigger) triggers.get(i));
} else {
JobRunShell shell = null;
try {
//重点创建一个新的任务
shell = this.qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
shell.initialize(this.qs);
} catch (SchedulerException var28) {
this.qsRsrcs.getJobStore().triggeredJobComplete((OperableTrigger) triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
continue;
}
//将任务在另一个线程池执行
if (!this.qsRsrcs.getThreadPool().runInThread(shell)) {
this.getLog().error("ThreadPool.runInThread() return false!");
this.qsRsrcs.getJobStore().triggeredJobComplete((OperableTrigger) triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
}
}
}
所以每次执行任务都会重新创建一个新的任务。这个任务的实例不会存放到Spring容器中。
任务中无法使用Spring的Bean解决方案:
1.重写SpringBeanJobFactory。重写这个工厂的createJobInstance方法,在创建完任务实例后,使用Spring提供的autowireBean方法去注入相应的Bean到这个任务实例的成员变量。
class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory {
private transient AutowireCapableBeanFactory beanFactory;
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;
}
}
2.将这个工厂配置到Quartz。
@Bean
public JobFactory jobFactory(ApplicationContext applicationContext) {
AutowiringSpringBeanJobFactory factory = new AutowiringSpringBeanJobFactory();
factory.setApplicationContext(applicationContext);
return factory;
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) throws IOException {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setJobFactory(jobFactory);
factoryBean.setOverwriteExistingJobs(true);
return factoryBean;
}
这样配置后任务就能使用Spring的容器的Bean了。
第二个问题,模板代码多。
可以使用父类模板方法设计模式来解决。
public abstract class AbstractQuartzJobBean extends QuartzJobBean{
public JobDetail getJobDetail() {
return JobBuilder.newJob(this.getClass()).storeDurably(true).withIdentity(getJobName(), getJobGroup()).build();
}
public Set<Trigger> getTriggers() {
HashSet<Trigger> triggers = new HashSet<>(1);
triggers.add(TriggerBuilder.newTrigger()
.withIdentity(getJobName(), getJobGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(getCron())).build());
return triggers;
}
/**
* 任务名称
*
* @return
*/
protected abstract String getJobName();
/**
* 任务组
*
* @return
*/
protected abstract String getJobGroup();
/**
* 获取Cron表达式
*
* @return
*/
protected abstract String getCron();
}
任务实现这个超类,实现相应的getJobName,getJobGroup,getCron方法就能被QUARTZ调度。比如像下面这样。
@Component
public class Test extends AbstractQuartzJobBean {
@Override
protected String getJobName() {
return "test";
}
@Override
protected String getJobGroup() {
return "test";
}
@Override
protected String getCron() {
return "*/5 * * * * ?";
}
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("test");
}
}
不过这样操作之后,任务是不会被执行的,因为我们还没有执行scheduler.scheduleJob方法。如果每增加一个任务就自己添加一句scheduler.scheduleJob很容易忘记,也很麻烦。
不知道有没有细心的同学发现我再Test测试Job上加上了@Component注解。前面刚说了任务会被每次重新创建,为什么还自己去添加Spring Bean的注解。这个注解是为了让我们偷个懒不用每增加一个任务就自己添加一句scheduler.scheduleJob。
@Component
public class QuartzJobRegister implements ApplicationRunner {
@Autowired
private List<AbstractQuartzJobBean> schedulables;
@Autowired
private Scheduler scheduler;
@Override
public void run(ApplicationArguments args) throws Exception {
for (Schedulable schedule : schedulables) {
this.scheduler.scheduleJob(schedule.getJobDetail(), schedule.getTriggers(), true);
}
}
}
使用一个Bean实现ApplicationRunner注解,在启动的时候将所有的任务执行scheduler.scheduleJob就不用自己手动的为每个任务添加了。
经过上面的操作后,每次新增任务就只用像Test任务那样实现AbstractQuartzJobBean,重新几个简单的提供信息的方法和任务执行内容方法就可以了。
---------------------------------------------------------------------------------------------------------------------------
源码地址:https://gitee.com/johnny-SW/spring-quartz
----------------------------------------------------------------------------------------------------------------------------
Maven
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
本文地址:https://blog.csdn.net/LCBUSHIHAHA/article/details/107066577
推荐阅读
-
如何在Spring Boot中使用Quartz
-
如何在Spring Boot中使用Cookies
-
详解Spring Boot中MyBatis的使用方法
-
Spring Boot 中 spring.jpa.open-in-view 使用注意点
-
spring boot中关于获取配置文件注解的使用@ConfigurationProperties、@Value、@PropertySource
-
嵌入式Redis服务器在Spring Boot测试中的使用教程
-
Spring Boot2.X中findOne的使用详解
-
如何在Intellij idea中使用Tomcat(spring boot)
-
如何在Intellij idea中使用Tomcat(spring boot)
-
如何在Spring Boot应用程序中使用配置文件