摘要
本文将带您讲解如何一步步实现通过quartz实现添加、修改、删除、停止job,以及优化quartz工具类,支持自动停止逻辑
本文将带您讲解如何一步步实现通过quartz实现添加、修改、删除、停止job,以及优化quartz工具类,支持自动停止逻辑
QuartzJobComponent.java
package com.example.demo.utils; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.quartz.TriggerUtils; import org.quartz.impl.triggers.CronTriggerImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.example.demo.constant.GloabalConstant; import com.example.demo.entity.QuartzJobModule; import lombok.extern.slf4j.Slf4j; /** * @project: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: qz工具类 * @version 1.0.0 * @errorcode * 错误码: 错误描述 * @author * <li>2020-09-04 825338623@qq.com Create 1.0 * @copyright ©2019-2020 。 */ @Slf4j @Component public class QuartzJobComponent { @Autowired private Scheduler scheduler; /** * @Description: 添加一个定时任务 * @param quartzModel */ public void addJob(QuartzJobModule quartzModel) { if (quartzModel.verify()) { try { JobDetail job = JobBuilder.newJob(quartzModel.getJobClass()) .withIdentity(quartzModel.getJobName(), quartzModel.getJobGroupName()) .setJobData(quartzModel.getJobDataMap()).build(); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzModel.getCron()); // 按新的cronExpression表达式构建一个新的trigger Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(quartzModel.getTriggerName(), quartzModel.getTriggerGroupName()) .startAt(quartzModel.getStartTime()).endAt(quartzModel.getEndTime()).withSchedule( scheduleBuilder) .build(); scheduler.scheduleJob(job, trigger); // 启动 if (!scheduler.isShutdown()) { scheduler.start(); } } catch (SchedulerException e) { log.error("Add quartz job error, jobName = {}", quartzModel.getJobName()); } } else { log.error("QuartzModel is invalid!"); } } /** * @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名) * @param jobName * @param cron */ public void modifyJobTime(String jobName, String cron, Date startDate, Date endDate) { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME); try { CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); if (trigger == null) { return; } String oldTime = trigger.getCronExpression(); if (!oldTime.equalsIgnoreCase(cron)) { CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey) .startAt(startDate) .endAt(endDate).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description:修改任务,(可以修改任务名,任务类,触发时间) * 原理:移除原来的任务,添加新的任务 * @param oldJobName * :原任务名 * @param jobName * @param jobclass * @param cron */ public void modifyJob(String oldJobName, String jobName, Class jobclass, String cron) { /* * removeJob(oldJobName); * addJob(jobName, jobclass, cron); * System.err.println("修改任务"+oldJobName); */ TriggerKey triggerKey = TriggerKey.triggerKey(oldJobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME); JobKey jobKey = JobKey.jobKey(oldJobName, GloabalConstant.QZ_JOB_GROUP_NAME); try { Trigger trigger = (Trigger) scheduler.getTrigger(triggerKey); if (trigger == null) { return; } scheduler.pauseTrigger(triggerKey);// 停止触发器 scheduler.unscheduleJob(triggerKey);// 移除触发器 scheduler.deleteJob(jobKey);// 删除任务 System.err.println("移除任务:" + oldJobName); JobDetail job = JobBuilder.newJob(jobclass).withIdentity(jobName, GloabalConstant.QZ_JOB_GROUP_NAME) .build(); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); // 按新的cronExpression表达式构建一个新的trigger Trigger newTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME) .withSchedule(scheduleBuilder).build(); // 交给scheduler去调度 scheduler.scheduleJob(job, newTrigger); // 启动 if (!scheduler.isShutdown()) { scheduler.start(); System.err.println("添加新任务:" + jobName); } System.err.println("修改任务【" + oldJobName + "】为:" + jobName); } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description: 修改一个任务的触发时间 * @param triggerName * @param triggerGroupName * @param cron */ public void modifyJobTime(String triggerName, String triggerGroupName, String cron) { TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName); try { CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); if (trigger == null) { return; } String oldTime = trigger.getCronExpression(); if (!oldTime.equalsIgnoreCase(cron)) { // trigger已存在,则更新相应的定时设置 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行 scheduler.resumeTrigger(triggerKey); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description 移除一个任务(使用默认的任务组名,触发器名,触发器组名) * @param jobName */ public void removeJob(String jobName) { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME); JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME); try { Trigger trigger = (Trigger) scheduler.getTrigger(triggerKey); if (trigger == null) { return; } scheduler.pauseTrigger(triggerKey);// 停止触发器 scheduler.unscheduleJob(triggerKey);// 移除触发器 scheduler.deleteJob(jobKey);// 删除任务 System.err.println("移除任务:" + jobName); } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description: 移除一个任务 * @param jobName * @param jobGroupName * @param triggerName * @param triggerGroupName */ public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, triggerGroupName); JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); try { scheduler.pauseTrigger(triggerKey);// 停止触发器 scheduler.unscheduleJob(triggerKey);// 移除触发器 scheduler.deleteJob(jobKey);// 删除任务 } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description:暂停一个任务(使用默认组名) * @param jobName */ public void pauseJob(String jobName) { JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME); try { scheduler.pauseJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @Description:暂停一个任务 * @param jobName * @param jobGroupName */ public void pauseJob(String jobName, String jobGroupName) { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); try { scheduler.pauseJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @Description:恢复一个任务(使用默认组名) * @param jobName */ public void resumeJob(String jobName) { JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME); try { scheduler.resumeJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @Description:恢复一个任务 * @param jobName * @param jobGroupName */ public void resumeJob(String jobName, String jobGroupName) { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); try { scheduler.resumeJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @Description:启动所有定时任务 */ public void startJobs() { try { scheduler.start(); } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description 关闭所有定时任务 */ public void shutdownJobs() { try { if (!scheduler.isShutdown()) { scheduler.shutdown(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。 * @param jobName */ public void triggerJob(String jobName) { JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME); try { scheduler.triggerJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。 * @param jobName * @param jobGroupName */ public void triggerJob(String jobName, String jobGroupName) { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); try { scheduler.triggerJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @Description: 获取任务状态 * @param jobName * 触发器名 */ public String getTriggerState(String jobName) { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME); String name = null; try { Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey); name = triggerState.name(); } catch (SchedulerException e) { e.printStackTrace(); } return name; } /** * @Description:获取最近5次执行时间 * @param cron */ public List<String> getRecentTriggerTime(String cron) { List<String> list = new ArrayList<String>(); try { CronTriggerImpl cronTriggerImpl = new CronTriggerImpl(); cronTriggerImpl.setCronExpression(cron); List<Date> dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 5); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); for (Date date : dates) { list.add(dateFormat.format(date)); } } catch (ParseException e) { log.error("GetRecentTriggerTime error, cron = {}", cron, e); } return list; } }
该包中的类为quartz中job调用时实际业务处理类
package com.example.demo.qzComp; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import lombok.extern.slf4j.Slf4j; /** * @project: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: quartz job 业务处理类 * @version 1.0.0 * @errorcode * 错误码: 错误描述 * @author * <li>2020-09-04 825338623@qq.com Create 1.0 * @copyright ©2019-2020 */ @DisallowConcurrentExecution @Slf4j public class TaskJobDetail extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { log.info("begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>"); String batchId = context.getJobDetail().getKey().getName(); log.info("执行的任务id为:[{}]", batchId); } }
注意:
该类为实际业务处理类,定时任务调起时,所有的业务都在此处写(该类不能使用Spring的注解,需要通过上下文来引入需要的bean)
我们可以看到,在该类中有一行代码:String batchId = context.getJobDetail().getKey().getName();该行代码会获取job的名称,我们可以在添加job的时候,将要处理的数据的唯一标识(比如id)设置为jobName,然后在这个业务处理类中就知道到底要处理哪个数据了。
该包中模拟的是前端请求添加、暂停、修改、停止、删除job的接口,在这个类中,同样有封装job的代码
package com.example.demo.controller; import java.util.Calendar; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.example.demo.common.Result; import com.example.demo.constant.GloabalConstant; import com.example.demo.entity.QuartzJobModule; import com.example.demo.qzComp.TaskJobDetail; import com.example.demo.utils.CronUtil; import com.example.demo.utils.QuartzJobComponent; /** * @project: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: quartz controller 。 * @version 1.0.0 * @errorcode * 错误码: 错误描述 * @author * <li>2020-09-04 825338623@qq.com Create 1.0 * @copyright ©2019-2020 */ @RestController @RequestMapping("/job") public class JobController { private final static Logger LOGGER = LoggerFactory.getLogger(JobController.class); @Autowired private QuartzJobComponent quartzJobComponent; @PostMapping("/add") @ResponseBody public Result save() { LOGGER.info("新增任务"); try { QuartzJobModule quartzJobModule = new QuartzJobModule(); Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 2020); cal.set(Calendar.MONTH, 8); cal.set(Calendar.DATE, 9); cal.set(Calendar.HOUR_OF_DAY, 12); cal.set(Calendar.MINUTE, 30); cal.set(Calendar.SECOND, 00); Date startDate = cal.getTime();// 任务开始日期为2020年9月9日12点30分 Calendar endCal = Calendar.getInstance(); endCal.set(Calendar.YEAR, 2020); endCal.set(Calendar.MONTH, 8); endCal.set(Calendar.DATE, 12); endCal.set(Calendar.HOUR_OF_DAY, 12); endCal.set(Calendar.MINUTE, 30); endCal.set(Calendar.SECOND, 00); Date endDate = endCal.getTime();// 任务结束日期为2020年9月12日12点30分 quartzJobModule.setStartTime(CronUtil.getStartDate(startDate)); quartzJobModule.setEndTime(CronUtil.getEndDate(endDate)); // 注意:在后面的任务中需要通过这个JobName来获取你要处理的数据,因此您可以讲这个设置为你要处理的数据的主键,比如id quartzJobModule.setJobName("testJobId"); quartzJobModule.setTriggerName("tesTriggerNmae"); quartzJobModule.setJobGroupName(GloabalConstant.QZ_JOB_GROUP_NAME); quartzJobModule.setTriggerGroupName(GloabalConstant.QZ_TRIGGER_GROUP_NAME); String weeks = "1,2,3,5";// 该处模拟每周1,2,3,5执行任务 String cronExpression = CronUtil .convertCronExpression(startDate, endDate, weeks.split(",")); quartzJobModule.setCron(cronExpression); quartzJobModule.setJobClass(TaskJobDetail.class); quartzJobComponent.addJob(quartzJobModule); } catch (Exception e) { e.printStackTrace(); return Result.ok(); } return Result.ok(); } @PostMapping("/edit") @ResponseBody public Result edit() { LOGGER.info("编辑任务"); Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 2020); cal.set(Calendar.MONTH, 8); cal.set(Calendar.DATE, 12); cal.set(Calendar.HOUR_OF_DAY, 12); cal.set(Calendar.MINUTE, 30); cal.set(Calendar.SECOND, 00); Date startDate = cal.getTime();// 任务开始日期为2020年9月12日12点30分 Calendar endCal = Calendar.getInstance(); endCal.set(Calendar.YEAR, 2020); endCal.set(Calendar.MONTH, 8); endCal.set(Calendar.DATE, 24); endCal.set(Calendar.HOUR_OF_DAY, 12); endCal.set(Calendar.MINUTE, 30); endCal.set(Calendar.SECOND, 00); Date endDate = endCal.getTime();// 任务结束日期为2020年9月24日12点30分 // "testJobId"为add方法添加的job的name quartzJobComponent.modifyJobTime("testJobId", "/10 * * ? * *", startDate, endDate); return Result.ok(); } @PostMapping("/pause") @ResponseBody public Result pause(String jobName, String jobGroup) { LOGGER.info("停止任务"); quartzJobComponent.pauseJob("testJobId"); return Result.ok(); } @PostMapping("/resume") @ResponseBody public Result resume(String jobName, String jobGroup) { LOGGER.info("恢复任务"); quartzJobComponent.removeJob("testJobId"); return Result.ok(); } @PostMapping("/remove") @ResponseBody public Result remove(String jobName, String jobGroup) { LOGGER.info("移除任务"); quartzJobComponent.removeJob("testJobId"); return Result.ok(); } }
注意:
夲示例在添加job时,jobGroupName和triggerGroupName都是用的是上面常量类中的常量,您在实际使用中,必须保证添加时的jobGroupName和triggerGroupName和后面要进行暂停、停止等操作时的值完全一致,否则不会起作用
我们知道,cron表达式还是有一些鸡肋的,比如无法设置9:50-10:10分的定时任务,因此我们在设置代码时尽量规避这类任务(CronUtil.convertCronExpression的作用就是生成一个cron表达式),如果您认为和您的业务有差距,请换成您自己的逻辑即可
至此,所有的配置和代码都完成了,我们需要调用以下进行验证是否能够成功动态添加job,并且成功调起job
访问http://localhost:8080/quartz/job/add 接口,此时数据库中的表中将会新增进入数据
同时我们稍等几秒,控制台上就会打印出如下内容:
2020-09-10 11:31:46.272 INFO 15308 --- [nio-8080-exec-1] c.example.demo.controller.JobController : 新增任务 2020-09-10 11:31:46.339 INFO 15308 --- [nio-8080-exec-1] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started. 2020-09-10 11:32:26.086 INFO 15308 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore : Handling 1 trigger(s) that missed their scheduled fire-time. 2020-09-10 11:32:26.151 INFO 15308 --- [eduler_Worker-1] com.example.demo.qzComp.TaskJobDetail : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>> 2020-09-10 11:32:26.151 INFO 15308 --- [eduler_Worker-1] com.example.demo.qzComp.TaskJobDetail : 执行的任务id为:[testJobId] 2020-09-10 11:32:30.010 INFO 15308 --- [eduler_Worker-2] com.example.demo.qzComp.TaskJobDetail : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>> 2020-09-10 11:32:30.011 INFO 15308 --- [eduler_Worker-2] com.example.demo.qzComp.TaskJobDetail : 执行的任务id为:[testJobId] 2020-09-10 11:32:35.023 INFO 15308 --- [eduler_Worker-3] com.example.demo.qzComp.TaskJobDetail : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>> 2020-09-10 11:32:35.023 INFO 15308 --- [eduler_Worker-3] com.example.demo.qzComp.TaskJobDetail : 执行的任务id为:[testJobId]
说明程序调用成功,并且在任务处理类中成功获取到了jobName,可以继续进行后面的业务处理
其他操作不在验证,大家可以自己调用进行测试,现在把代码放出来:
github :https://github.com/wangmingweikong/spring-boot/tree/master/spring-boot-quartz