Spring Boot集成Quartz
我们常用的定时任务调度有如下几种:
1. 使用JDK自带的类库实现
- 通过继承TimeTask,使用Time来调度任务
- 使用ScheduledExecutorService来实现任务调度
2,Spring 自带了任务调度功能,通过使用@Schedule()注解来实现
3,使用Quartz框架,Quartz可以用于在分布式环境下的任务调度
Quartz的基本概念:
1,Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)
2,JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
3,Trigger 代表一个调度参数的配置,什么时候去调。
4,Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
配置环境依赖
添加Maven依赖:
org.springframework.boot spring-boot-starter-quartz
修改application.yml文件,添加:
spring: datasource: name: schedule driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/schedule?useUnicode=true&characterEncoding=utf-8 username: root password: 123456 quartz: job-store-type: jdbc
quartz支持两种存储方式,一个使用数据库,也就是job-store-type=jdbc,需要你下载quartz提供的表,并导入到你本地,然后配置你的数据库连接,官方下载地址, 另一种是内存方式,job-store-type设置为momery
配置任务
创建一个任务配置,这里的数据可以对应数据库一条记录
public class ScheduleTaskConfig { private Integer id; private String name; private String groupName; private String describe; private String cron; private String classPath; private Character isEnabled; private String createTime; private String updateTime; public String getClassName() { try { Class cls = Class.forName(classPath); return cls.getSimpleName(); } catch (ClassNotFoundException e) { log.error("获取Class失败",e); } return null; } }
创建用于操作Quartz任务创建、暂停、恢复方法
Service @Slf4j public class QuartzService { @Autowired private Scheduler scheduler; public void createJob(ScheduleTaskConfig config) { Class cls = null; try { cls = Class.forName(config.getClassPath()); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("config", config); JobDetail jobDetail = JobBuilder.newJob(cls) .withIdentity(config.getName(), config.getGroupName()) .withDescription(config.getDescribe()) .setJobData(jobDataMap) .storeDurably() .build(); Trigger trigger = TriggerBuilder.newTrigger() .forJob(jobDetail) .withIdentity(jobDetail.getKey().getName(), GROUP_NAME) .withSchedule(CronScheduleBuilder.cronSchedule(config.getCron())) .build(); scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { log.error("创建任务失败", e); } } /** * 获取任务状态 * @return */ public String getJobState(ScheduleTaskConfig config) { try { TriggerKey triggerKey = TriggerKey.triggerKey(config.getName(), config.getGroupName()); Trigger.TriggerState state = scheduler.getTriggerState(triggerKey); return state.name(); } catch (Exception e) { log.error("创建任务失败", e); } return null; } /** * 判断Job是否存在 * @param config * @return */ public boolean isExistJob(ScheduleTaskConfig config) { try { TriggerKey triggerKey = TriggerKey.triggerKey(config.getName(), config.getGroupName()); CronTrigger trigger = (CronTrigger)scheduler.getTrigger(triggerKey); if (trigger != null) { return true; } } catch (Exception e) { log.error("获取任务trigger失败", e); } return false; } /** * 更新Job任务 * @param config */ public void updateJob(ScheduleTaskConfig config) { try { TriggerKey triggerKey = TriggerKey.triggerKey(config.getName(), config.getGroupName()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(config.getCron()); //按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey()); // 忽略状态为PAUSED的任务,解决集群环境中在其他机器设置定时任务为PAUSED状态后,集群环境启动另一台主机时定时任务全被唤醒的bug if(!triggerState.name().equalsIgnoreCase("PAUSED")){ //按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } } catch (Exception e) { log.error("更新任务trigger失败", e); } } /** * 停止任务 * @param jobName * @param groupName * @return */ public boolean pauseJob(String jobName) { try { JobKey jobKey = JobKey.jobKey(config.getName(), config.getGroupName()); scheduler.pauseJob(jobKey); return true; } catch (SchedulerException e) { log.error("停止任务失败", e); } return false; } /** * 恢复任务 * @param jobName * @param groupName * @return */ public boolean resumeJob(String jobName) { try { JobKey jobKey = JobKey.jobKey(config.getName(), config.getGroupName()); scheduler.resumeJob(jobKey); return true; } catch (SchedulerException e) { log.error("恢复任务失败", e); } return false; } /** * 删除任务 * @param jobName * @param groupName * @return */ public boolean deleteJob(String jobName) { try { JobKey jobKey = JobKey.jobKey(config.getName(), config.getGroupName()); scheduler.deleteJob(jobKey); return true; } catch (SchedulerException e) { log.error("删除任务失败", e); } return false; }
可以看到Quartz创建任务就是通过JobDetail和Trigger来实现定时任务的,JobDetail声明了任务的一些信息,比如名称、分组、描述、需要执行类(例如:com.chuanz.task.CheckExecutor)需要的数据(通过调用setJobData方法) Trigger设置了任务的执行周期及触发时间,这里通过cron表达式来设置触发,另外Trigger还有另外一种触发方式,通过SimpleScheduleBuilder来实现,比如:
.withSchedule(SimpleScheduleBuilder.repeatMinutelyForTotalCount(10, 5))
我需要重复执行10次,每隔5分钟执行一次
创建我们需要执行的任务,实现Job接口,并重写execute方法
@Slf4j @Service public class CheckExecutor implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { log.info("执行计划冲突类型审核开始"); // 获取数据 ScheduleTaskConfig config = (ScheduleTaskConfig)context.get("config"); /** * 处理逻辑 */ log.info("执行计划冲突类型审核结束"); } }
我们可以将quartz创建、删除、停止、恢复任务都封装成一个个接口,通过前端页面来管理quartz的任务,具体前端页面代码我就不贴了,基本就是调用QuartzService里面的方法了,另外说说获取quartz任务列表的时候,我们可以获取Trigger里面响应的执行状态,通过Trigger里面的方法来获取,比如:
JobKey jobKey = new JobKey(config.getName(), config.getGroupName()); List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey); if (triggers.size() > 0) { Trigger trigger = triggers.get(0); //开始执行时间 trigger.getStartTime(); //上一次执行时间 trigger.getPreviousFireTime(); //下一次执行时间 trigger.getNextFireTime(); }
trigger有很多实现,比如我们这里使用的是cron表达式,所以相对应的就是CronTriggerImpl,常用的触发器有四种。
触发器介绍
SimpleTrigger:简单的触发器
指定从某一个时间开始,以一定的时间间隔,如秒、分、小时重复执行的任务
比如:从10:00 开始,每隔1小时/1分钟/1秒钟执行一次,执行10次。
对应的方法如下:
CalendarIntervalTrigger:日历触发器
类似于SimpleTrigger,指定从某一个时间开始,以一定的时间间隔执行的任务。 但是不同的是SimpleTrigger指定的时间间隔为毫秒,没办法指定每隔一个月执行一次(每月的时间间隔不是固定值),而CalendarIntervalTrigger支持的间隔单位有秒,分钟,小时,天,月,年,星期。
对应的方法如下:
DailyTimeIntervalTrigger:日期触发器
指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。 它适合的任务类似于:指定每天9:00 至 18:00 ,每隔70秒执行一次,并且只要周一至周五执行。 比如如下代码:
DailyTimeIntervalTrigger trigger = dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(10, 0)) //从10:00点开始 .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(22, 0)) //22:00点结束 .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一到周五执行 .withIntervalInHours(1) //每间隔1小时执行一次 .withRepeatCount(50) //最多重复50次(实际执行50+1次) .build();
CronTrigger:Cron表达式触发器
适合于更复杂的任务,它支持类型于Linux Cron的语法(并且更强大)。基本上它覆盖了以上三个Trigger的绝大部分能力
属性只有一个就是cron表达式
我们可以根据对应的触发器取到任务执行的数据,如之前用SimpleScheduleBuilder.repeatMinutelyForTotalCount(10, 5),那么对应的就是SimpleTrigger,我们可以获取执行的次数,剩余的次数等。 触发器的介绍可以参考官方文档