springboot Quartz動態修改cron表達式


適用於:

     動態修改定時任務,根據數據庫的定時任務進行任務的激活和暫停,帶參定時任務,指定時間和執行次數的定時任務等。

1、概述

  在開發中有的時候需要去手動禁止和啟用定時任務,修改定時任務的cron表達式然后再讓其動態生效,之前有過SSM的類似的業務的開發但是忘記寫下來了。。。只好重新溫習了一次,加上最近比較流行springBoot所以升級了一下用springBoot來完成.

2、關聯技術

SpringBoot、Quartz、mysql、thymeleaf (好像就這么多)

3、涉及核心API 類

  1. Scheduler – 調度器,定時任務的指派和停止、啟用、刪除、列舉都由他得出;
  2. Job – 通過scheduler執行任務,任務類需要實現的接口;
  3. JobDetail – 定義Job的實例;
  4. Trigger – 觸發Job的執行,旗下分 CronTrigger 根據Cron表達式動態執行和SimpleTrigger 根據指定時間和間隔去執行;
  5. JobBuilder – 定義和創建JobDetail實例的接口;
  6. TriggerBuilder – 定義和創建Trigger實例的接口;

4、具體流程 

        1)首先去手動創建一個調度器工廠對象-SchedulerFactoryBean;其實應該不用手動創建的但是為了顧及到業務的復雜性所以還是創建一個好用。

1  @Bean
2     public SchedulerFactoryBean schedulerFactory(){
3         SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
4         /*用於Quartz集群,啟動時更新已存在的Job*/
5         factoryBean.setOverwriteExistingJobs(true);
6         /*定時任務開始啟動后延遲5秒開始*/
7         factoryBean.setStartupDelay(5);
8         return factoryBean;
9 }

         2)繼承job,實現方法execute。此處手寫的原因是因為我們需要對定時任務的功能進行擴展,比如文中提到的參數注入和執行日志記錄.注意jobExecutionContext.getMergedJobDataMap() 它可以獲得 JobDataMap這個是Job實例化的一些信息,可以用於定時任務的帶參。如下:

 1 import com.study.www.enums.ConfigEnum;
 2 import com.study.www.model.Config;
 3 import com.study.www.model.Logger;
 4 import com.study.www.model.mapper.LoggerRepository;
 5 import com.study.www.utils.TaskUtils;
 6 import org.quartz.DisallowConcurrentExecution;
 7 import org.quartz.Job;
 8 import org.quartz.JobExecutionContext;
 9 import org.quartz.JobExecutionException;
10 import org.springframework.scheduling.annotation.Async;
11 import org.springframework.stereotype.Component;
12 
13 import java.util.ArrayList;
14 import java.util.List;
15 
16 
17 //當上一個任務未結束時下一個任務需進行等待
18 @DisallowConcurrentExecution
19 @Component
20 public class MyJob implements Job {
21 
22     //注意此處的 Dao 直接 @Autowired 注入是獲取不到的,我們可以通過Spring容器去進行手動注入
23     static LoggerRepository loggerRepository;
24     //定時任務日志落地
25     static List<Logger> loggers=new ArrayList<>();
26     //每10條日志進行一下落地
27     private final static Integer SIZE=9;
28 
29     //execute會根據cron的規則進行執行
30     @Override
31     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
32         Config config = (Config) jobExecutionContext.getMergedJobDataMap().get(ConfigEnum.SCHEDULEJOB.getCode());
33         Long beginTime=null;
34         if (config.getIsLogger()){
35             beginTime=System.currentTimeMillis();
36         }
37         TaskUtils.invokMethod(config);
38         if (beginTime != null) {
39             //手動注入 Dao
40             if (MyJob.loggerRepository == null){
41                 MyJob.loggerRepository = SpringUtils.getBean("loggerRepository");
42             }
43             saveSysLog(beginTime,config);
44         }
45     }
46 
47 
48     @Async
49     void saveSysLog(Long beginTime, Config config) {
50         Logger logger = new Logger();
51         logger.setBeginTime(beginTime);
52         logger.setEndTime(System.currentTimeMillis());
53         logger.setClassPath(config.getClassPath());
54         logger.setMethName(config.getMethodName());
55         logger.setName(config.getName());
56         logger.setGroupName(config.getGroup());
57         logger.setTime(System.currentTimeMillis()-beginTime);
58         logger.setParams(config.getReqParms());
59         if (loggers.size() > SIZE) {
60             synchronized (MyJob.class) {
61                 loggerRepository.save(loggers);
62                 loggers.clear();
63             }
64         }else{
65             loggers.add(logger);
66         }
67     }
68 
69 }

 

            3)獲取到調度器-Scheduler和JobBuilder以及TriggerBuilder

 1 /*****************  此三者均可復用,類似於 Factory 一樣。故提出做公用 ********/
 2     //調度器 
 3     private static Scheduler scheduler;
 4     //JobBuilder
 5     private static JobBuilder jobBuilder;
 6     //觸發器Builder
 7     private static TriggerBuilder<Trigger> triggerBuilder ;
 8     
 9 
10     //毫秒的間隔時間
11     private static final Integer MILLISECONDS=1000;
12 
13     @PostConstruct
14     public void init(){
15         //因為Job 被自己給實現了故此處應去指定對應的Job類去創建一個調度器
16         QuartzTableManager.jobBuilder= JobBuilder.newJob(MyJob.class);
17         QuartzTableManager.scheduler = schedulerFactoryBean.getScheduler();
18         triggerBuilder = TriggerBuilder.newTrigger();
19     }

            4)實現動態新增、刪除定時任務的方法.此處的 trigger 觸發機制有兩種 一種是Cron 一種是 選擇時間和時間間隔的。因為有一種業務情況為客戶去手動選擇時間進行定時任務發送。

 1 /**
 2      * 增加任務
 3      *
 4      * @param :com.study.www.model.config
 5      * @Date: 2018/2/26 9:57
 6      * @return: void
 7      */
 8      void addJob(Config config) throws SchedulerException {
 9         //得到調度器
10         JobKey jobKey = this.getJobKey(config);
11         //獲得觸發器
12         TriggerKey triggerKey = TriggerKey.triggerKey(config.getName(), config.getGroup());
13         Trigger trigger = scheduler.getTrigger(triggerKey);
14         //判斷觸發器是否存在(如果存在說明之前運行過但是在當前被禁用了,如果不存在說明一次都沒運行過)
15         if (trigger == null) {
16             //新建一個工作任務 指定任務類型為串接進行的
17             JobDetail jobDetail = jobBuilder.withIdentity(jobKey).build();
18             //將工作添加到工作任務當中去
19             JobDataMap jobDataMap = jobDetail.getJobDataMap();
20             jobDataMap.put(ConfigEnum.SCHEDULEJOB.getCode(), config);
21             trigger = getTrigger(config, triggerKey);
22             //在調度器中將觸發器和任務進行組合
23             scheduler.scheduleJob(jobDetail, trigger);
24         } else {
25             //按照新的規則進行
26             trigger = getTrigger(config, triggerKey);
27             //重啟
28             scheduler.rescheduleJob(triggerKey, trigger);
29         }
30     }
31     
32     /**
33     * 注意 simpleTrigger 和 cronTrigger 互斥只可以選擇一個
34     */
35     private  Trigger getTrigger(Config config,TriggerKey triggerKey ){
36         Trigger trigger =null;
37         if(config.getCount() == null){
38             //將cron表達式進行轉換
39             CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(config.getCron());
40             trigger = triggerBuilder.withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
41         }else{
42             //指定的時間和時間段來進行定時任務
43             MutableTrigger build = SimpleScheduleBuilder.simpleSchedule()
44                     .withRepeatCount(config.getCount())
45                     .withIntervalInMilliseconds(config.getIntervalSecond() * MILLISECONDS)
46                     .build();
47             build.setStartTime(config.getStartTime());
48             build.setEndTime(config.getEndTime());
49             build.setKey(triggerKey);
50             trigger = (Trigger)build;
51         }
52         return trigger;
53     }
54 
55     /**
56      * 刪除任務
57      *
58      * @param : com.study.www.model.config
59      * @Date: 2018/2/24 18:23
60      * @return: void
61      */
62      void deleteJob(Config config) throws SchedulerException {
63         //找到key值
64         JobKey jobKey = this.getJobKey(config);
65         //從觸發器找到此任務然后進行刪除
66         scheduler.deleteJob(jobKey);
67     }
68 
69     /**
70      * 根據name和group得到任務的key
71      *
72      * @param :com.study.www.model.config
73      * @Date: 2018/2/24 18:27
74      * @return: org.quartz.JobKey
75      */
76      JobKey getJobKey(Config config) {
77         return getJobKey(config.getName(), config.getGroup());
78     }
79 
80      JobKey getJobKey(String name, String group) {
81         return JobKey.jobKey(name, group);
82     }

         5)創建對定時任務的方法進行反射執行的方法。如下:

 1 import com.alibaba.fastjson.JSON;
 2 import com.alibaba.fastjson.JSONObject;
 3 import com.study.www.config.SpringUtils;
 4 import com.study.www.model.Config;
 5 import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
 6 
 7 import java.lang.reflect.Method;
 8 import java.lang.reflect.Parameter;
 9 
10 public class TaskUtils {
11 
12     private static LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer;
13 
14     public static void invokMethod(Config config) {
15         Object obj = null;
16         Class clazz = null;
17         //通過Spring上下文去找 也有可能找不到
18         try {
19             obj = SpringUtils.getBean(config.getClassPath().split("\\.")[config.getClassPath().split("\\.").length - 1]);
20             if (obj == null) {
21                 clazz = Class.forName(config.getClassPath());
22                 obj = clazz.newInstance();
23             } else {
24                 clazz = obj.getClass();
25             }
26         } catch (Exception e) {
27             throw new RuntimeException("ERROR:TaskUtils is Bean Create please check the classpath is`t right or not");
28         }
29         //方法執行
30         try {
31             //入參
32             JSONObject jsonObject = null;
33             String reqParms = config.getReqParms();
34             if (reqParms != null && reqParms.length() > 0) {
35                 jsonObject = JSON.parseObject(reqParms);
36             }
37             //獲得方法名
38             Method[] methods = clazz.getMethods();
39             for (Method method1 : methods) {
40                 //不帶參的
41                 if ((config.getMethodName().equals(method1.getName()) && jsonObject== null) ){
42                     method1.invoke(obj);
43                     return;
44                 }
45                 //帶參的
46                 if ((config.getMethodName().equals(method1.getName()) && method1.getParameterCount()== jsonObject.size()) ){
47                     Object[] params = new Object[method1.getParameterCount()];
48                     if (localVariableTableParameterNameDiscoverer == null){
49                         localVariableTableParameterNameDiscoverer= new LocalVariableTableParameterNameDiscoverer();
50                     }
51                     String[] parameterNames = localVariableTableParameterNameDiscoverer.getParameterNames(method1);
52                     Parameter[] parameters = method1.getParameters();
53                     for (int i = 0; i < parameterNames.length; i++) {
54                         Class<?> type = parameters[i].getType();
55                         Object object = jsonObject.getObject(parameterNames[i], type);
56                         params[i]=object;
57                     }
58                     method1.invoke(obj,params);
59                     return;
60                 }
61             }
62         } catch (Exception e) {
63             throw new RuntimeException("ERROR:TaskUtils is Bean the method execute please check the methodName is`t right or not");
64         }
65     }
66 }

      提示:  頁面用戶修改Cron 表達式如果需要對Cron表達式進行校驗可以使用如下方法。

//Cron表達式解析
 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(config.getCron());

   提示: 如果定時任務的帶參為不固定的比如是另外的一個請求等,可以修改 步驟 5中的規則。例如可以在Config 中加個用來進行參數類型判斷的字段,然后參數為 一個http請求這樣可以做到在定時任務中進行一個請求后再任務的執行。

若時間充裕推薦這封博客其對Quartz將的更加詳細,配合示例看可以有着事半功倍的效果。http://ifeve.com/quartz-tutorial-using-quartz/

代碼例子:

碼雲:https://gitee.com/zhuyanpengWorld/springboots/blob/master/quartz.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM