前言
在SpringBoot中,自身提供了@Scheduled等注解实现简单的定时任务,但在面对较复杂的定时需求时,仍然需要使用quartz,即一有定时处理的需求,应该优先使用quartz来处理。
下面,将介绍在SpringBoot2.0中如何整合quartz,免去以前那种繁杂的xml配置方式,不涉及数据库保存定时记录等复杂内容。
SpringBoot版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
一、开始添加定时任务前的配置
1、在pom.xml中引入依赖
<!--spring boot2.x以前quartz相关依赖-->
<!--<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>${quartz.version}</version>
</dependency>-->
<!--spring boot2.x后quartz依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2、添加quartz的properties配置
在resources下,新建application-quartz.yml,内容如下
#quartz相关属性配置
spring:
quartz:
properties:
org:
quartz:
scheduler:
#scheduler实例名称与id分配
instanceName: clusteredScheduler
instanceId: AUTO
#job的保存设置
# jobStore:
# class: org.quartz.impl.jdbcjobstore.JobStoreTX
# driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# tablePrefix: QRTZ_
# isClustered: true
# clusterCheckinInterval: 10000
# useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
#数据库方式,默认使用memory的方式(此时不需要配置jobStore)
# job-store-type: jdbc
#初始化表结构
# jdbc:
# initialize-schema: never
在本例子中,使用memory的方式保存定时任务,所以不需要配置jobStore,其他配置意思见注释
3、配置自定义的Scheduler
新建类‘SchedulerCustomizer’,继承‘SchedulerFactoryBeanCustomizer’
/**
* @date 2019/3/3
* @des 配置自定义的Scheduler;注意,若在此设定scheduler延时启动N秒,
* 则job的启动时间(包括延时启动)便要大于或等于这个延时时间N
*/
@Configuration
@EnableScheduling
public class SchedulerCustomizer implements SchedulerFactoryBeanCustomizer{
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setStartupDelay(5);//程序启动后5秒启动定时scheduler,注意这里的设置
schedulerFactoryBean.setAutoStartup(true);//job自启动
schedulerFactoryBean.setOverwriteExistingJobs(true);//覆盖已存在的job
}
}
接下来,便可以添加定时任务,进行测试啦。
定时任务的添加分为动态与静态两种,下面分别介绍。
二、添加定时任务
A、静态添加
新建一个类‘QuartzConfiguration’,如
/**
* @des 第1种创建job任务的方式,即通过配置类;每一个job都需要在此配值,提供两个bean,一个JobDetail,一个Trigger
*/
@Configuration
public class QuartzConfiguration {
}
使用SimpleSchedule
1、新建一个job,如
/**
* @des quartz 简单测试类
*/
@Slf4j
public class FirstJob extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String param = jobExecutionContext.getJobDetail().getJobDataMap().getString("first");
log.info("--FirstJob--:{} --" + LocalDateTime.now(), param);
}
}
2、在‘QuartzConfiguration’中配置相应的bean,如
/**
* @des 每一个job都需要在此配值,提供两个bean,一个JobDetail,一个Trigger
*/
@Configuration
public class QuartzConfiguration {
/*---使用simpleSchedule来添加任务---*/
// 使用jobDetail包装job
@Bean
public JobDetail firstJobDetail() {
//创建任务
JobDetail jobDetail = JobBuilder.newJob(FirstJob.class).withIdentity("firstJob").storeDurably().build();
//通过任务传递参数
jobDetail.getJobDataMap().put("first", "hello world!");
return jobDetail;
}
// 把jobDetail注册到trigger上去
@Bean
public Trigger firstJobTrigger() {
//创建自己的任务调度器
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(30).repeatForever();
//创建任务触发器
return TriggerBuilder.newTrigger()
.forJob(firstJobDetail())
.withIdentity("firstJobTrigger")
.withSchedule(scheduleBuilder) //将触发器与任务绑定到调度器内
//.startNow() //使用startNow()马上启动不生效,因为scheduler还没启动
//.startAt(new Date())//使用startAt(new Date())也不能生效,因为scheduler还没启动
//.startAt(new Date(System.currentTimeMillis()))//也不生效,因为scheduler还没启动
.startAt(new Date(System.currentTimeMillis() + 5000))//只有延时时间大于等于scheduler延时启动时间才行
.build();
}
}
在这里,涉及job启动时间的疑问,即‘设置job启动时间时,存在不生效的问题’
原因是
1.由于在SchedulerCustomizer设置了scheduler延时启动,所以scheduler并不是跟随程序同步启动的
2.在job中设置的延时时间是从程序启动后开始算的,而不是scheduler启动后再重新算的延时时间
3.设置startNow()不生效的原因,是因为scheduler还没到启动时间(即scheduler此时还没启动),所以无效
4.也就是说,要使job跟scheduler同步启动,则job需要设置延时启动,而且时间大于或等于scheduler的延时启动时间
-
当‘SchedulerCustomizer’开启了延时启动后,要使job跟随scheduler马上启动或第一次延时启动,则需要设置job的启动时间,且该启动时间必须是在scheduler启动后才行,否则,只有等到job的下一次定时到达后,才执行定时任务。
-
当‘SchedulerCustomizer’没设置延时启动,则job的启动时间不受限制
这时,运行程序,打印日志如下
2019-03-03 17:15:39.246 INFO 1508 --- [uartzScheduler]] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now, after delay of 5 seconds
2019-03-03 17:15:39.247 INFO 1508 --- [uartzScheduler]] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2019-03-03 17:15:39.274 INFO 1508 --- [eduler_Worker-1] com.he.schedule.job.FirstJob : --FirstJob--:hello world! --2019-03-03T17:15:39.274
可见 Scheduler在程序启动5秒后启动,同时FirstJob的延时时间到,此时Scheduler也已启动,所以第一次执行定时任务,否则只能等待下一次定时到达才执行。
使用CronSchedule
1、新建一个job,如
/**
* @des quartz cron任务测试类
*/
@Slf4j
public class FirstCronJob extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("--FirstCronJob--:{}" , LocalDateTime.now());
}
}
2、在‘QuartzConfiguration’中配置相应的bean,如
/**
* @des 每一个job都需要在此配值,提供两个bean,一个JobDetail,一个Trigger
*/
@Configuration
public class QuartzConfiguration {
/*---使用simpleSchedule来添加任务---*/
// 使用jobDetail包装job
@Bean
public JobDetail firstJobDetail() {
//创建任务
JobDetail jobDetail = JobBuilder.newJob(FirstJob.class).withIdentity("firstJob").storeDurably().build();
//通过任务传递参数
jobDetail.getJobDataMap().put("first", "hello world!");
return jobDetail;
}
// 把jobDetail注册到trigger上去
@Bean
public Trigger firstJobTrigger() {
//创建自己的任务调度器
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(30).repeatForever();
//创建任务触发器
return TriggerBuilder.newTrigger()
.forJob(firstJobDetail())
.withIdentity("firstJobTrigger")
.withSchedule(scheduleBuilder) //将触发器与任务绑定到调度器内
//.startNow() //使用startNow()马上启动不生效,因为scheduler还没启动
//.startAt(new Date())//使用startAt(new Date())也不能生效,因为scheduler还没启动
//.startAt(new Date(System.currentTimeMillis()))//也不生效,因为scheduler还没启动
.startAt(new Date(System.currentTimeMillis() + 5000))//只有延时时间大于等于scheduler延时启动时间才行
.build();
}
/*---使用cron表达式来创建任务---*/
// 使用jobDetail包装job
@Bean
public JobDetail firstCronJobDetail() {
return JobBuilder.newJob(FirstCronJob.class).withIdentity("firstCronJob").storeDurably().build();
}
// 把jobDetail注册到Cron表达式的trigger上去
@Bean
public Trigger firstCronJobTrigger() {
//创建自己的任务调度器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 * * * * ?");//从第0秒开始,每隔30秒执行一次
return TriggerBuilder.newTrigger()
.forJob(firstCronJobDetail())
.withIdentity("firstCronJobTrigger")
.withSchedule(cronScheduleBuilder)
.startAt(new Date(System.currentTimeMillis() + 5000))
.build();
}
}
启动程序,观察日志打印如下
2019-03-03 17:40:07.177 INFO 3664 --- [ main] com.he.SpringbootlessonApplication : Started SpringbootlessonApplication in 8.586 seconds (JVM running for 9.143)
2019-03-03 17:40:12.143 INFO 3664 --- [uartzScheduler]] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now, after delay of 5 seconds
2019-03-03 17:40:12.143 INFO 3664 --- [uartzScheduler]] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2019-03-03 17:40:12.160 INFO 3664 --- [eduler_Worker-2] com.he.schedule.job.FirstCronJob : --FirstCronJob--:2019-03-03T17:40:12.160
2019-03-03 17:40:12.160 INFO 3664 --- [eduler_Worker-1] com.he.schedule.job.FirstJob : --FirstJob--:hello world! --2019-03-03T17:40:12.160
至此,静态添加job任务方式介绍完毕,这种方式比较适合伴随程序启动时启动的定时场景。
B、动态添加以及动态对定时任务进行修改,删除等操作
1、创建‘ScheduleManager’类,添加操作定时任务的封装代码(封装得比较粗犷)
/**
* @date 2019/3/3
* @des schedule任务管理类,动态实现开始,暂停,停止等操作
*/
@Slf4j
@Component
public class ScheduleManager {
@Autowired
private Scheduler scheduler;
/**
* 使用SimpleScheduleBuilder创建一个定时任务
* @param jobName job名称
* @param jobGroupName job分组名称
* @param triggerName 触发器名称
* @param triggerGroupName 触发器分组名称
* @param jobClass job的class名
* @param durability 是否持久化
* @param interval 间隔时间,单位秒
* @param delay 延迟启动时间,单位秒
* @return
*/
public Boolean addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
boolean durability, int interval, int delay) {
/*
* 1.取到任务调度器Scheduler
* 2.定义jobDetail;
* 3.定义trigger;
* 4.使用Scheduler添加任务;
*/
try {
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).storeDurably(durability).build();
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(interval).repeatForever();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName, triggerGroupName)
.withSchedule(scheduleBuilder)
.startAt(new Date(new Date().getTime() + delay * 1000))
.build();
scheduler.scheduleJob(jobDetail, trigger);
return true;
} catch (SchedulerException e) {
e.printStackTrace();
}
return false;
}
/**
* 使用CronScheduleBuilder创建一个定时任务
*
* @param jobName job名称
* @param jobGroupName job分组名称
* @param triggerName 触发器名称
* @param triggerGroupName 触发器分组名称
* @param jobClass job的class名
* @param durability 是否持久化
* @param cronExpression 定时表达式
* @param delay 延迟启动时间,单位秒
* @return
*/
public Boolean addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
boolean durability, String cronExpression, int delay) {
/*
* 1.取到任务调度器Scheduler
* 2.定义jobDetail;
* 3.定义trigger;
* 4.使用Scheduler添加任务;
*/
try {
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).storeDurably(durability).build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName, triggerGroupName)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.startAt(new Date(new Date().getTime() + delay * 1000))
.build();
scheduler.scheduleJob(jobDetail, trigger);
return true;
} catch (SchedulerException e) {
e.printStackTrace();
}
return false;
}
/**
* 动态停止job任务
* @param jobName job名称
* @param jobGroupName job分组名称
* @return
*/
public Boolean pause(String jobName, String jobGroupName) {
try {
JobKey key = new JobKey(jobName, jobGroupName);
scheduler.pauseJob(key);
return true;
} catch (SchedulerException e) {
e.printStackTrace();
}
return false;
}
/**
* 动态开始job任务
*
* @param jobName job名称
* @param jobGroupName job分组名称
* @return
*/
public Boolean start(String jobName, String jobGroupName) {
try {
JobKey key = new JobKey(jobName, jobGroupName);
scheduler.resumeJob(key);
return true;
} catch (SchedulerException e) {
e.printStackTrace();
}
return false;
}
/**
* 移除定时任务
*
* @param jobName job名称
* @param jobGroupName job分组名称
* @param triggerName 触发器名称
* @param triggerGroupName 触发器分组名称
* @return
*/
public Boolean delete(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
try {
Integer status =getJobStatus(triggerName, triggerGroupName);
if (status == 0) {
//NONE - 0,该job不存在
log.warn("NONE - 0,该job不存在");
return null;
}
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);//通过触发器名和组名获取TriggerKey
JobKey jobKey = new JobKey(jobName, jobGroupName); //通过任务名和组名获取JobKey
scheduler.pauseTrigger(triggerKey); //停止触发器
scheduler.unscheduleJob(triggerKey);//移除触发器
scheduler.deleteJob(jobKey);// 删除任务
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 动态修改任务执行的时间
*
* @param type 创建类型,1-cron,2-simple
* @param jobName job名称
* @param jobGroupName job分组名称
* @param triggerName 触发器名称
* @param triggerGroupName 触发器分组名称
* @param time type为1时-填cron表达式,为2-整数
* @return
*/
public Boolean modify(String type,String jobName, String jobGroupName, String triggerName, String triggerGroupName, String time) {
try {
Integer status =getJobStatus(triggerName, triggerGroupName);
if (status == 0) {
//NONE - 0,该job不存在
log.warn("NONE - 0,该job不存在");
return null;
}
//获取任务
JobKey key = new JobKey(jobName, jobGroupName);
//获取jobDetail
JobDetail jobDetail = scheduler.getJobDetail(key);
//生成trigger
if ("1".equals(type)) {
// 1.CronSchedule
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(triggerName, triggerGroupName)
.withSchedule(CronScheduleBuilder.cronSchedule(time))
.build();
//删除旧的任务,否则报错
//scheduler.deleteJob(key);
delete(jobName, jobGroupName, triggerName, triggerGroupName);
//重新启动任务
scheduler.scheduleJob(jobDetail, trigger);
return true;
}
if ("2".equals(type)) {
// 2.SimpleSchedule
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(triggerName, triggerGroupName)
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(Integer.valueOf(time)))
.build();
//删除旧的任务,否则报错
//scheduler.deleteJob(key);
delete(jobName, jobGroupName, triggerName, triggerGroupName);
//重新启动任务
scheduler.scheduleJob(jobDetail, trigger);
return true;
}
} catch (SchedulerException e) {
e.printStackTrace();
}
return false;
}
/**
* 查看job运行状态
* @param triggerName 触发器名称
* @param triggerGroupName 触发器分组名称
* @return TriggerState枚举值: NONE - 0;NORMAL - 1;PAUSED - 2;COMPLETE - 3;ERROR - 4;BLOCKED - 5
*/
public Integer getJobStatus(String triggerName, String triggerGroupName) {
/*
TriggerState枚举值
NONE - 0,
NORMAL -1,
PAUSED - 2,
COMPLETE - 3,
ERROR - 4,
BLOCKED - 5;
*/
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
return scheduler.getTriggerState(triggerKey).ordinal();
} catch (SchedulerException e) {
e.printStackTrace();
}
return null;
}
/**
* 启动所有定时任务
*/
public Boolean startAll() {
try {
scheduler.start();
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 关闭所有定时任务
*/
public Boolean shutdownAll() {
try {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
封装的十分粗犷,有很大优化空间。
2、创建一个job
/**
* @des
*/
@Slf4j
public class SecondJob extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("second---: {}" , LocalTime.now());
}
}
3、接着就在需要操作定时任务的地方,注入“ScheduleManager”进行增删改即可,eg
/**
* @des 继承ApplicationRunner,在此可进行一些程序启动后的操作,如启动一个定时任务
* 在此操作定时任务,将使得程序启动后便会执行该定时逻辑,适合伴随程序启动的定时场景
*/
@Slf4j
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Autowired
private ScheduleManager scheduleManager;
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
log.info("--MyApplicationRunner--");
scheduleManager.addJob("test","test","test","test",
SecondJob.class,false,10,5);
}
}
程序启动后,打印日志如下
2019-03-03 18:08:27.404 INFO 2652 --- [ main] com.he.SpringbootlessonApplication : Started SpringbootlessonApplication in 7.25 seconds (JVM running for 7.759)
2019-03-03 18:08:27.407 INFO 2652 --- [ main] com.he.system.MyApplicationRunner : --MyApplicationRunner--
2019-03-03 18:08:32.374 INFO 2652 --- [uartzScheduler]] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now, after delay of 5 seconds
2019-03-03 18:08:32.374 INFO 2652 --- [uartzScheduler]] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2019-03-03 18:08:32.420 INFO 2652 --- [eduler_Worker-1] com.he.schedule.job.SecondJob : second---: 18:08:32.420
以上这种动态方式,具有较大灵活性。适合一些根据代码逻辑,动态对定时任务进行修改的场景。
至此,Springboot2.0整合quartz完毕,以上内容仅为作者学习过程整理的笔记,有误的地方欢迎指正。