2.1.2.RELEASE
版本。其实通过分析SpringBoot对于Quartz的自动化配置源码,也有助于我们理解Quartz的使用org.springframework.boot.autoconfigure.quartz
# Web工程
org.springframework.boot
spring-boot-starter-web
# quartz
org.springframework.boot
spring-boot-starter-quartz
# 数据库JDBC
org.springframework.boot
spring-boot-starter-jdbc
# 使用MySql
mysql
mysql-connector-java
runtime
# Job 实现
@DisallowConcurrentExecution
public class DemoJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("~~ DemoJob 启动运行汇总~~");
}
}
# JobDetail、Trigger Bean配置
@Configuration
public class QuartzJobConfig {
@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
jobDetail.setName("DemoJob");
jobDetail.setGroup("DemoJob_Group");
jobDetail.setJobClass(DemoJob.class);
jobDetail.setDurability(true);
return jobDetail;
}
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean() {
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(jobDetailFactoryBean().getObject());
trigger.setCronExpression("*/10 * * * * ?");
trigger.setName("DemoJob");
trigger.setMisfireInstruction(0);
return trigger;
}
}
SpringBoot关于Quartz的自动配置的类一共有6个,分别为:
JobStoreType
:是一个枚举类,定义了jobsStore的类型,基于内存和数据库QuartzAutoConfiguration
:自动配置类,配置了Quartz的主要组件,如SchedulerFactoryBean
QuartzDataSource
:是一个注解类。如果需要注入Quartz配置的数据库操作类,需要使用此注解标注。参考QuartzAutoConfiguration
中的用法QuartzDataSourceInitializer
:该类主要用于数据源初始化后的一些操作,根据不同平台类型的数据库进行选择不同的数据库脚本QuartzProperties
:该类对应了在application.yml
配置文件以spring.quartz
开头的相关配置SchedulerFactoryBeanCustomizer
:在自动配置的基础上自定义配置需要实现的此接口。QuartzAutoConfiguration
QuartzProperties
SchedulerFactoryBeanCustomizer
JobDetail
、Trigger
、Calendar
。所以我们只需要进行 JobDetail、Trigger
Bean配置,会自动注入进QuartzAutoConfiguration
类中,这边是通过ObjectProvider
的使用实现的。 public QuartzAutoConfiguration(QuartzProperties properties,
ObjectProvider customizers,
ObjectProvider jobDetails,
ObjectProvider
SchedulerFactoryBean
的详细信息。这个类是一个 FactoryBean
。JobFactory
,内部设置了applicationContext
与spring容器结合QuartzProperties
类实现JobDetail
、Trigger
、Calendar
SchedulerFactoryBeanCustomizer
实现。这边包括自定义,也包括基于数据库实现的JobStore
配置。 @Bean
@ConditionalOnMissingBean
public SchedulerFactoryBean quartzScheduler() {
# 配置 `JobFactory`
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
jobFactory.setApplicationContext(this.applicationContext);
schedulerFactoryBean.setJobFactory(jobFactory);
# 开始配置各种属性
if (this.properties.getSchedulerName() != null) {
schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
}
schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());
schedulerFactoryBean
.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(
this.properties.isWaitForJobsToCompleteOnShutdown());
schedulerFactoryBean
.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
if (!this.properties.getProperties().isEmpty()) {
schedulerFactoryBean
.setQuartzProperties(asProperties(this.properties.getProperties()));
}
# 配置 jobDetails、triggers等
if (this.jobDetails != null && this.jobDetails.length > 0) {
schedulerFactoryBean.setJobDetails(this.jobDetails);
}
if (this.calendars != null && !this.calendars.isEmpty()) {
schedulerFactoryBean.setCalendars(this.calendars);
}
if (this.triggers != null && this.triggers.length > 0) {
schedulerFactoryBean.setTriggers(this.triggers);
}
# 自定义配置
customize(schedulerFactoryBean);
return schedulerFactoryBean;
}
JobStore
配置,内部类JdbcStoreTypeConfiguration
@ConditionalOnSingleCandidate(DataSource.class)
指定pring容器中有且只有一个指明的DataSource
Bean时生效dataSourceCustomizer
方法实现schedulerFactoryBean
的数据库相关配置,该方法返回一个 SchedulerFactoryBeanCustomizer
。QuartzDataSourceInitializer
数据库初始化 BeanDataSourceInitializerSchedulerDependencyPostProcessor
实现对于QuartzDataSourceInitializer
这个Bean的依赖关系(dependsOn) @Bean
@Order(0)
public SchedulerFactoryBeanCustomizer dataSourceCustomizer(
QuartzProperties properties, DataSource dataSource,
@QuartzDataSource ObjectProvider quartzDataSource,
ObjectProvider transactionManager) {
return (schedulerFactoryBean) -> {
# 判断是否为 JobStore
if (properties.getJobStoreType() == JobStoreType.JDBC) {
# 获取 DataSource
DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
# 配置 DataSource 和 TransactionManager管理
schedulerFactoryBean.setDataSource(dataSourceToUse);
PlatformTransactionManager txManager = transactionManager.getIfUnique();
if (txManager != null) {
schedulerFactoryBean.setTransactionManager(txManager);
}
}
};
}
QuartzProperties
@ConfigurationProperties("spring.quartz")
以spring.quartz
开头的配置spring:
quartz:
scheduler-name: springboot-quartz-jdbc-dynamic
auto-startup: false
startup-delay: 5s
overwrite-existing-jobs: false
wait-for-jobs-to-complete-on-shutdown: true
job-store-type: memory
# jdbc:
# initialize-schema: embedded
# schema: classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql
# comment-prefix: --
properties: {
org.quartz.scheduler.instanceName: springboot-quartz-jdbc-dynamic,
org.quartz.scheduler.instanceId: AUTO,
org.quartz.threadPool.class: org.springframework.scheduling.quartz.SimpleThreadPoolTaskExecutor,
org.quartz.threadPool.threadCount: 25,
org.quartz.threadPool.threadPriority: 5,
org.quartz.jobStore.misfireThreshold: 60000,
# org.quartz.jobStore.tablePrefix: QRTZ_,
# org.quartz.jobStore.isClustered: true,
# org.quartz.jobStore.clusterCheckinInterval: 20000,
# org.quartz.jobStore.maxMisfiresToHandleAtATime: 1,
# org.quartz.jobStore.txIsolationLevelSerializable: false
}
SpringBoot-2.1.2.RELEASE
任务调度框架(4)Quartz 分布式实现 已经对Quartz自身的分布式实现做了简单的介绍,这边主要基于SpringBoot怎么做。
job-store-type
可以选择JDBC
完成分布式JdbcJobStore
切换jdbc.XXX
主要是对于初始化SQL的配置。树妖是对于quartz提供的11张表的初始化sqlJdbcJobStore
的一些特殊配置,如表前缀、集群指定、数据库检查等,基于RamJobStore
时,这些是不允许配置的。spring:
quartz:
job-store-type: memory
jdbc:
initialize-schema: embedded
schema: classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql
comment-prefix: --
properties: {
org.quartz.jobStore.misfireThreshold: 60000,
org.quartz.jobStore.tablePrefix: QRTZ_,
org.quartz.jobStore.isClustered: true,
org.quartz.jobStore.clusterCheckinInterval: 20000,
org.quartz.jobStore.maxMisfiresToHandleAtATime: 1,
org.quartz.jobStore.txIsolationLevelSerializable: false
}
jdbc.xxx
配置没有生效,个人是自己手动初始化的表。JobDetail
、Trigger
的Bean配置public class BatchTask extends AbstractDataEntity {
/**
* 任务编码:唯一
*/
private String code;
/**
* 任务名称
*/
private String name;
/**
* 任务描述
*/
private String description;
/**
* 前置任务
*/
private List previous;
}
public class BatchSchedule extends AbstractDataEntity {
/**
* 计划编码
*/
private String code;
/**
* 计划名称
*/
private String name;
/**
* 计划状态: 整个生命周期状态
*/
private Integer status;
/**
* 执行表达式类型
*/
private Integer cronType;
/**
* 执行表达式
*/
private String cronExpression;
/**
* 描述
*/
private String description;
/**
* 处理业务类
*/
private String interfaceName;
/**
* 任务编码(任务组的概念)
*/
private String taskCode;
/**
* 开始时间(最近)
*/
private Date startDate;
/**
* 结束时间(最近)
*/
private Date endDate;
/**
* 前置计划列表
*/
private List dependencies;
/**
* 参数列表
*/
private List params;
}
public class BatchScheduleParam {
/**
* 任务计划ID
*/
private String scheduleId;
/**
* 任务计划code
*/
private String scheduleCode;
/**
* 参数名
*/
private String paramName;
/**
* 参数值
*/
private String paramValue;
}
JobDetailFactoryBean
、CronTriggerFactoryBean
,然后通过SpringBoot的自动化配置,设置到schedulerFactoryBean
对象的对应属性中。schedulerFactoryBean.setJobDetails(this.jobDetails);
schedulerFactoryBean.setTriggers(this.triggers);
SchedulerFactoryBean
源码中,通过afterPropertiesSet()
方法中方法,注册到Scheduler
对象中Scheduler.scheduleJob(jobDetail, trigger);
添加 // Initialize the Scheduler instance...
this.scheduler = prepareScheduler(prepareSchedulerFactory());
try {
registerListeners();
registerJobsAndTriggers(); # 注册JobsAndTriggers
}
protected void registerJobsAndTriggers() throws SchedulerException {
// Register JobDetails.
if (this.jobDetails != null) {
for (JobDetail jobDetail : this.jobDetails) {
addJobToScheduler(jobDetail);
}
}
// Register Triggers.
if (this.triggers != null) {
for (Trigger trigger : this.triggers) {
addTriggerToScheduler(trigger);
}
}
}
private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException {
if (this.overwriteExistingJobs || getScheduler().getJobDetail(jobDetail.getKey()) == null) {
# 最终是通过Scheduler.addJob(jobDetail, true); 添加
getScheduler().addJob(jobDetail, true);
return true;
}
else {
return false;
}
}
private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
# 最终是通过Scheduler.scheduleJob(jobDetail, trigger); 添加(只是一部分功能)
getScheduler().scheduleJob(jobDetail, trigger);
}
Scheduler
在SpringBoot中已经通过SchedulerFactoryBean
自动配置好了,直接注入即可使用。public class QuartzScheduleEngine {
@Autowired
private Scheduler scheduler;
/**
* 新增计划任务: 主要是添加 jobDetail 和 trigger
*
* @param batchSchedule
*/
public Date addJob(BatchSchedule batchSchedule) throws Exception {
String cronExpression = batchSchedule.getCronExpression();
String name = batchSchedule.getCode();
String group = batchSchedule.getTaskCode();
String interfaceName = batchSchedule.getInterfaceName();
// 校验数据
this.checkNotNull(batchSchedule);
// 添加 1-JobDetail
// 校验 JobDetail 是否存在
JobKey jobKey = JobKey.jobKey(name, group);
if (scheduler.checkExists(jobKey)) {
if (Strings.isNullOrEmpty(cronExpression)) {
// 已经存在并且执行一次,立即执行
scheduler.triggerJob(jobKey);
} else {
throw new Exception("任务计划 JobKey 已经在执行队列中,不需要重复启动");
}
} else {
// 构建 JobDetail
Class extends Job> jobClazz = (Class extends Job>) Class.forName(interfaceName);
JobDetail jobDetail = JobBuilder.newJob(jobClazz).withIdentity(jobKey).build();
jobDetail.getJobDataMap().put(BatchSchedule.SCHEDULE_KEY, batchSchedule.toString());
// 添加 2-Trigger
// 校验 Trigger 是否存在
TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
Trigger trigger = scheduler.getTrigger(triggerKey);
if (Objects.nonNull(trigger)) {
throw new Exception("任务计划 Trigger 已经在执行队列中,不需要重复启动");
}
// 构建 Trigger
trigger = getTrigger(cronExpression, triggerKey);
return scheduler.scheduleJob(jobDetail, trigger);
}
return new Date();
}
/**
* 修改
*
* @param batchSchedule
*/
public void updateCronExpression(BatchSchedule batchSchedule) throws Exception {
updateJobCronExpression(batchSchedule);
}
/**
* 更新Job的执行表达式
*
* @param batchSchedule
* @throws SchedulerException
*/
public Date updateJobCronExpression(BatchSchedule batchSchedule) throws SchedulerException {
checkNotNull(batchSchedule);
String name = batchSchedule.getCode();
String group = batchSchedule.getTaskCode();
TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
// 在队列中才需要修改
if (scheduler.checkExists(triggerKey)) {
// 构建 Trigger
String cronExpression = batchSchedule.getCronExpression();
Trigger trigger = this.getTrigger(cronExpression, triggerKey);
return scheduler.rescheduleJob(triggerKey, trigger);
}
return null;
}
/**
* 构建 Trigger
*
* @param cronExpression
* @param triggerKey
* @return
*/
private Trigger getTrigger(String cronExpression, TriggerKey triggerKey) {
Trigger trigger;
if (Strings.isNullOrEmpty(cronExpression.trim())) {
trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).build();
} else {
cronExpression = cronExpression.replaceAll("#", " ");
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
}
return trigger;
}
/**
* 暂停计划任务
*
* @param batchSchedule
*/
public void pauseJob(BatchSchedule batchSchedule) throws Exception {
checkNotNull(batchSchedule);
JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
if (!scheduler.checkExists(jobKey)) {
throw new Exception("任务计划不在执行队列中,不能暂停");
}
scheduler.pauseJob(jobKey);
}
/**
* 从暂停中恢复
*
* @param batchSchedule
*/
public void resumeJob(BatchSchedule batchSchedule) throws Exception {
checkNotNull(batchSchedule);
JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
if (!scheduler.checkExists(jobKey)) {
throw new Exception("任务计划不在执行队列中,不能恢复");
}
scheduler.resumeJob(jobKey);
}
/**
* 删除计划任务
*
* @param batchSchedule
*/
public boolean deleteJob(BatchSchedule batchSchedule) throws SchedulerException {
boolean flag = true;
checkNotNull(batchSchedule);
JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
if (scheduler.checkExists(jobKey)) {
flag = scheduler.deleteJob(jobKey);
}
return flag;
}
/**
* 添加任务监听
*
* @param jobListener
* @param matcher
* @throws SchedulerException
*/
public void addJobListener(JobListener jobListener, Matcher matcher) throws SchedulerException {
scheduler.getListenerManager().addJobListener(jobListener, matcher);
}
/**
* 执行一次(可用于测试)
*
* @param batchSchedule
*/
public void runJobOnce(BatchSchedule batchSchedule) throws SchedulerException {
checkNotNull(batchSchedule);
JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
scheduler.triggerJob(jobKey);
}
private void checkNotNull(BatchSchedule batchSchedule) {
Preconditions.checkNotNull(batchSchedule, "计划为空");
Preconditions.checkState(!StringUtils.isEmpty(batchSchedule.getCode()), "计划编号为空");
Preconditions.checkState(!StringUtils.isEmpty(batchSchedule.getTaskCode()), "计划所属任务为空");
Preconditions.checkState(!StringUtils.isEmpty(batchSchedule.getInterfaceName()), "任务执行业务类为空");
}
public SchedulerMetaData getMetaData() throws SchedulerException {
SchedulerMetaData metaData = scheduler.getMetaData();
return metaData;
}
}
JobListener
实现,需要自定义配置的支持public class CustomGlobalJobListener extends JobListenerSupport {
@Override
public String getName() {
return this.getClass().getName();
}
/**
* Scheduler 在 JobDetail 将要被执行时调用这个方法。
*
* @param context
*/
@Override
public void jobToBeExecuted(JobExecutionContext context) {
getLog().debug("计划 {} : ~~~ 【RUNNING】 更新正在运行中状态 ~~~ ");
}
/**
* Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法
*
* @param context
*/
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
}
/**
* Scheduler 在 JobDetail 被执行之后调用这个方法
*
* @param context
* @param jobException
*/
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
getLog().debug("计划 {} : ~~~ 【COMPLETE | ERROR】 更新已经结束状态 ~~~ ");
// 唤醒子任务
batchScheduleService.notifyChildren(scheduleJob);
}
}
SchedulerFactoryBeanCustomizer
接口@Configuration
public class SchedulerFactoryBeanCustomizerConfig implements SchedulerFactoryBeanCustomizer {
@Bean
public CustomGlobalJobListener globalJobListener() {
return new CustomGlobalJobListener();
}
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setGlobalJobListeners(globalJobListener());
}
}
至此,Quartz 的任务动态实现已经完成,主要可以分为三个部分:
JobListener
监听器实现JobListener
监听器实现。