当前位置:   article > 正文

自定义任务调度实现-org.quartz-scheduler

org.quartz-scheduler

实现目标

1.可以新建自定义调度任务,自定义调度能够配置cron表达式。

2.自定义调度任务可以禁用删除等操作,完成禁用删除操作需要停止自动调度任务。

3.可以查看调度任务每次执行任务情况。

4.自定义调度任务需要可以立即运行。

效果

 

技术选型

SpringBoot+Mybatis+org.quartz-scheduler

实现

首先自己实现一个调度任务这太麻烦了所以我找到了org.quartz-scheduler 他的功能刚好足够我实现上面的功能,现在我来整理一下为什么org.quartz-scheduler 能实现我的功能?

根据官网的文档我主要知道了 org.quartz-scheduler 主要有几个关键的API

Scheduler  调度器()

Job 由调度程序执行的组件实现的接口

JobDetail 定义Jobs的实例(也就是调度任务)

Trigger 触发器,定义执行给定作业时间表的组件(触发器又分为cron触发器和simple触发器,这两种触发器刚好可以满足我上述需求)

JobBuilder  构建JobDetail实例

TriggerBuilder 构建触发器实例

既然我知道了他几个关键的API能实现我的功能那我们怎么做了?首先我要知道他是怎么创建一个任务调度的并启动的

  1. SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
  2. Scheduler sched = schedFact.getScheduler();
  3. sched.start();
  4. // define the job and tie it to our HelloJob class
  5. JobDetail job = newJob(HelloJob.class)
  6. .withIdentity("myJob", "group1")
  7. .build();
  8. // Trigger the job to run now, and then every 40 seconds
  9. Trigger trigger = newTrigger()
  10. .withIdentity("myTrigger", "group1")
  11. .startNow()
  12. .withSchedule(simpleSchedule()
  13. .withIntervalInSeconds(40)
  14. .repeatForever())
  15. .build();
  16. // Tell quartz to schedule the job using our trigger
  17. sched.scheduleJob(job, trigger);

注意:HelloJob.class是继承Job这个接口,所以我们可以理解为job实例执行都需要继承这个job

根据官方给的这个例子我们大概知道了他大概流程是先创建一个Scheduler   然后启动Scheduler,最后加入job实例(也就是我们这次需求的调度任务),而job detail 需要和触发器绑定官方绑定的是一个触发器simpleSchedule()  然后在将job和触发器绑定到调度器中,根据这个简单的调度代码我们知道如果我们需要创建一个job实例我们就需要创建一个公共的SchedulerBuilder类来创建触发器

SchedulerBuilder 主要的功能如下:

 创建一个 Cron Job 实例,创建SimpleJob实例,停止调度任务,启动调度任务等功能

如下:

  1. public final class SchedulerBuilder {
  2. private static final Logger logger = LoggerFactory.getLogger(SchedulerBuilder.class);
  3. private static Scheduler schedulerFactory;
  4. static {
  5. schedulerFactory = newFactory();
  6. }
  7. public static Scheduler newFactory() {
  8. try {
  9. return new StdSchedulerFactory().getDefaultScheduler();
  10. } catch (SchedulerException e) {
  11. e.printStackTrace();
  12. }
  13. return null;
  14. }
  15. /**
  16. * 创建一个 Cron Scheduler
  17. *
  18. * @param name 任务名称
  19. * @param groupName job分组名称
  20. * @param cls 执行计划类
  21. * @param cron 表达式
  22. * @param data 参数
  23. * @throws SchedulerException
  24. */
  25. public static void newCronScheduler(String name, String groupName, Class cls, String cron, String triggerGroupName, String data) throws SchedulerException {
  26. JobBuilder jobBuilder = newJob(cls)
  27. .withIdentity(name, groupName);
  28. //添加数据参数
  29. JobDetail job = addData(jobBuilder, data).build();
  30. // 构建一个触发器,规定触发的规则
  31. Trigger trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器
  32. .withIdentity(name, triggerGroupName)// 给触发器起一个名字和组名
  33. .startNow()// 立即执行
  34. .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // 触发器的执行时间cors
  35. .build();// 产生触发器
  36. schedulerFactory.scheduleJob(job, trigger);
  37. }
  38. /**
  39. * 创建一个Simple Scheduler
  40. * 用于:特定时刻仅执行一次,或者在特定时刻然后以特定间隔重复执行
  41. *
  42. * @param name 任务名称
  43. * @param groupName job分组名称
  44. * @param cls 执行计划类
  45. * @param triggerGroupName trigger分组名称
  46. * @param data 参数
  47. * @throws SchedulerException
  48. */
  49. public static void newSimpleScheduler(String name, String groupName, Class cls, String triggerGroupName, String data) throws SchedulerException {
  50. JobBuilder jobBuilder = newJob(cls)
  51. .withIdentity(name, groupName);
  52. //添加数据参数
  53. JobDetail job = addData(jobBuilder, data).build();
  54. // 构建一个触发器,规定触发的规则
  55. Trigger trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器
  56. .withIdentity(name, triggerGroupName)// 给触发器起一个名字和组名
  57. .startNow()// 立即执行
  58. .withSchedule(simpleSchedule()) // 触发器的执行时间cors
  59. .build();// 产生触发器
  60. schedulerFactory.scheduleJob(job, trigger);
  61. }
  62. /**
  63. * 停止任务调度
  64. *
  65. * @param name 任务名称
  66. * @param groupName 任务分组名称
  67. * @param trigeerGroupName 触发器分组名称
  68. * @throws SchedulerException
  69. */
  70. public static void stopScheduler(String name, String groupName, String trigeerGroupName) throws SchedulerException {
  71. JobKey jobKey = new JobKey(name, groupName);
  72. schedulerFactory.pauseJob(jobKey); //停止任务
  73. schedulerFactory.unscheduleJob(new TriggerKey(name, trigeerGroupName));// 移除触发器
  74. schedulerFactory.deleteJob(jobKey); //删除job
  75. }
  76. /**
  77. * 检验是否存在任务调度
  78. *
  79. * @param name 任务名称
  80. * @param groupName 任务分组名称
  81. */
  82. public static boolean checkExistScheduler(String name, String groupName) throws SchedulerException {
  83. JobKey jobKey = new JobKey(name, groupName);
  84. boolean exist = schedulerFactory.checkExists(jobKey);
  85. return exist;
  86. }
  87. /**
  88. * 启动所有任务
  89. *
  90. * @throws SchedulerException
  91. */
  92. public static void startScheduler() throws SchedulerException {
  93. schedulerFactory.start();
  94. }
  95. /**
  96. * 关闭所有定时任务
  97. *
  98. * @throws SchedulerException
  99. */
  100. public static void shutdownScheduler() throws SchedulerException {
  101. if (!schedulerFactory.isShutdown()) {
  102. schedulerFactory.shutdown();
  103. }
  104. }
  105. /**
  106. * 添加JobDetails数据
  107. *
  108. * @param builder jobBuilder
  109. * @param text 数据源
  110. */
  111. private static JobBuilder addData(JobBuilder builder, String text) {
  112. if (StringUtils.isBlank(text)) {
  113. return builder;
  114. }
  115. JSONObject object = JSONUtils.parseObject(text);
  116. for (Map.Entry<String, Object> item : object.entrySet()) {
  117. builder.usingJobData(item.getKey(), item.getValue().toString());
  118. }
  119. return builder;
  120. }
  121. }

现在我们已经可以创建调度任务了那我们怎么去创建调度任务了,我的想法是创建任务是动态的所以我们需要实时加载所以我们需要在前台写一个任务调度页面大致如下:

 当我们创建了任务那他怎么去自动自定义调度的任务了所以我创建了两个默认的调度任务一个是系统cron启动调度任务和系统simple 启动调度任务,我只需要在服务启动时先运行这个两个系统调度任务,然后由这两个系统任务去创建其他的自定义调度任务,启动系统调度任务代码如下:

  1. /**
  2. * 运行系统定时任务
  3. */
  4. public void RunSystemSchedulers() throws SchedulerException {
  5. synchronized (runSystemSchedulersLock) {
  6. SchedulersParamPo schedulersParam = new SchedulersParamPo(SchedulerTypeEnum.System.value());
  7. //获取所有任务
  8. List<SysScheduler> schedulers = schedulerMapper.selectSchedulers(schedulersParam);
  9. for (SysScheduler sysScheduler : schedulers) {
  10. String className = sysScheduler.getClassName();
  11. Class cls = ClassUtils.getClassForName(className);
  12. SchedulerBuilder.newCronScheduler(sysScheduler.getJobName(), SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME, cls, sysScheduler.getCron(), SchedulerConstant.CRON_TRIGGER_DEFAULT_GROUP_NAME, sysScheduler.getJobData());
  13. }
  14. SchedulerBuilder.startScheduler();
  15. }
  16. }

系统cron启动调度任务

功能主要是将自定义调度任务启动起来,启动的时候根据状态判断是要启动还是要停止调度任务(停止调度任务会将此调度任务删除),运行和停止调度任务之前先判断是否存在过调度任务,然后将调度任务状态记录下来。代码如下

  1. public Map<String, Integer> RunSchedulers() throws SchedulerException {
  2. synchronized (runSchedulersLock) {
  3. Map<String, Integer> result = new LinkedHashMap<>(8);
  4. SchedulersParamPo schedulersParam = new SchedulersParamPo(SchedulerTypeEnum.Custom.value());
  5. //获取所有任务
  6. List<SysScheduler> schedulers = schedulerMapper.selectSchedulers(schedulersParam);
  7. result.put("all", schedulers.size());
  8. int buildCount, startCount, stopCount, errorCount, notFoundCount;
  9. buildCount = startCount = stopCount = errorCount = notFoundCount = 0;
  10. //循环创建job 但是不启动
  11. for (SysScheduler sysScheduler : schedulers) {
  12. String className = sysScheduler.getClassName();
  13. Class cls = ClassUtils.getClassForName(className);
  14. if (cls != null) {
  15. //创建的时候报错不影响其他自动化任务
  16. try {
  17. String jobName = sysScheduler.getJobName();
  18. boolean existScheduler = SchedulerBuilder.checkExistScheduler(jobName, SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME);
  19. //如果节点状态是停止状态,且存在任务则停止任务调度
  20. if (sysScheduler.getStatus() == SchedulerStatusEnum.STOP.value()) {
  21. if (existScheduler) {
  22. SchedulerBuilder.stopScheduler(jobName, SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME, SchedulerConstant.CRON_TRIGGER_DEFAULT_GROUP_NAME);
  23. }
  24. stopCount += 1;
  25. } else {
  26. if (!existScheduler) {
  27. SchedulerBuilder.newCronScheduler(jobName, SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME, cls, sysScheduler.getCron(), SchedulerConstant.CRON_TRIGGER_DEFAULT_GROUP_NAME, sysScheduler.getJobData());
  28. buildCount += 1;
  29. } else {
  30. startCount += 1;
  31. }
  32. }
  33. } catch (SchedulerException e) {
  34. logger.error(e.getMessage());
  35. errorCount += 1;
  36. stopCount += 1;
  37. }
  38. } else {
  39. logger.error(sysScheduler.getJobName() + " not fount class");
  40. notFoundCount += 1;
  41. stopCount += 1;
  42. }
  43. }
  44. result.put("build", buildCount);
  45. result.put("start", startCount);
  46. result.put("stop", stopCount);
  47. result.put("error", errorCount);
  48. result.put("not found", notFoundCount);
  49. if (buildCount > 0) {
  50. SchedulerBuilder.startScheduler();
  51. }
  52. return result;
  53. }
  54. }

系统simple 启动调度任务

功能主要是将需要临时立即运行的任务运行,运行逻辑是将需要立即运行的任务存入另外一张表和系统调度任务表是区分开的,然后根据调度记录每次运行10条临时运行任务

  1. /**
  2. * 运行系统定时任务
  3. */
  4. public Integer RunSimpleSchedulers() throws SchedulerException {
  5. synchronized (runSimpleSchedulersLock) {
  6. //获取所有任务
  7. List<SchedulerSimplePageViewPo> schedulers = schedulerSimpleMapper.selectSchedulerSimplePage();
  8. for (SchedulerSimplePageViewPo sysScheduler : schedulers) {
  9. try {
  10. String className = sysScheduler.getClassName();
  11. Class cls = ClassUtils.getClassForName(className);
  12. SchedulerBuilder.newSimpleScheduler(sysScheduler.getJobName(), SchedulerConstant.SIMPLE_JOB_DEFAULT_GROUP_NAME, cls, SchedulerConstant.SIMPLE_TRIGGER_DEFAULT_GROUP_NAME, sysScheduler.getJobData());
  13. updateSchedulerSimpleStatus(sysScheduler.getSchedulerExecuteSimpleId(), SchedulerSimpleStatusEnum.SUCCESS);
  14. } catch (SchedulerException e) {
  15. updateSchedulerSimpleStatus(sysScheduler.getSchedulerExecuteSimpleId(), SchedulerSimpleStatusEnum.ERROR);
  16. }
  17. }
  18. SchedulerBuilder.startScheduler();
  19. return schedulers.size();
  20. }
  21. }

注意:临时调度任务都会生成一个唯一的job name  由调度任务名称+"_"+uuid组成 

现在临时运行任务和自定义调度任务都能运行了,我们怎么去记录每个任务的执行时间了,因为所有运行的job实例都需要继承job接口所以我们实现了一个抽象类AbstractDefaultJob类来实现job接口,主要是实现excuse方法,然后提供一个run方法需要继承的类去实现,而我们在excute中实现相当于切面记录,代码如下:

  1. public abstract class AbstractDefaultJob implements Job {
  2. private static final Logger logger = LoggerFactory.getLogger(AbstractDefaultJob.class);
  3. /**
  4. * 执行异常
  5. */
  6. public void execute(JobExecutionContext context) throws JobExecutionException {
  7. long start = System.currentTimeMillis();
  8. String name = "";
  9. SchedulerSimpleJobTypeEnum schedulerSimpleJobTypeEnum = SchedulerSimpleJobTypeEnum.CRON;
  10. try {
  11. JobKey key = context.getTrigger().getJobKey();
  12. name = key.getName();
  13. if (SchedulerConstant.SIMPLE_JOB_DEFAULT_GROUP_NAME.equals(key.getGroup())) {
  14. schedulerSimpleJobTypeEnum = SchedulerSimpleJobTypeEnum.SIMPLE;
  15. }
  16. JobDataMap dataMap = context.getMergedJobDataMap();
  17. SchedulerExecuteResult result = run(dataMap);
  18. handleExecute(name, start, true, schedulerSimpleJobTypeEnum, result.getMessage());
  19. } catch (Exception e) {
  20. logger.error(e.getMessage());
  21. handleExecute(name, start, false, schedulerSimpleJobTypeEnum, "");
  22. }
  23. }
  24. /**
  25. * 执行处理
  26. *
  27. * @param schedulerName 任务名称
  28. * @param start 开始时间
  29. * @param succeess 是否成功
  30. * @param remark 备注
  31. */
  32. public void handleExecute(String schedulerName, long start, boolean succeess, SchedulerSimpleJobTypeEnum jobTypeEnum, String remark) {
  33. long end = System.currentTimeMillis();
  34. long executeInterval = end - start;
  35. SchedulerExecuteService schedulerExecuteService = SpringContext.getBean("schedulerExecuteServiceImpl");
  36. schedulerExecuteService.insertSchedulerExecute(schedulerName, start, end, executeInterval, succeess, jobTypeEnum, remark);
  37. }
  38. public <T> T getBean(Class cls) {
  39. String name = cls.getSimpleName();
  40. name = VariableNameConvert.firstLetterLower(name);
  41. return SpringContext.getBean(name);
  42. }
  43. public abstract SchedulerExecuteResult run(JobDataMap dataMap) throws SchedulerException;
  44. }

码云:youyue: 有岳,后台管理系统,springboot+mybatis+redis 

文档地址:

http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/778982
推荐阅读
  

闽ICP备14008679号