一. 整合的步驟
- 建立springboot工程映入相關依賴
- 導入quartz的tables_mysql_innodb.sql文件到數據庫中
- 建立中間數據控制表
- 建立Job工廠類
- 建立任務Trigger觸發器監聽類
- 建立業務控制處理類
- 項目重啟重置任務處理
- 配置quartz參數
二. 詳細描述
(1) 建立springboot工程映入相關依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
(2) 導入quartz的tables_mysql_innodb.sql文件到數據庫中
(3) 建立中間數據控制表
中間數據控制表用於管理任務的增刪改以及初始化
CREATE TABLE `sys_schedule_info` ( `id` varchar(100) NOT NULL, `task_description` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任務描述', `task_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任務名稱', `task_group` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任務組名稱', `trigger_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '觸發器名稱', `trigger_group` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '觸發器組', `trigger_cron_expression` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '表達式', `execute_class_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '目標執行類類名', `execute_method_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '執行類的具體執行方法', `target_table` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '數據目標所在表集合","分割用於統計', `is_start` tinyint(1) DEFAULT NULL COMMENT '是否啟動', `status` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '1' COMMENT '0刪,1允正常', `updated_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '創建人id', `updated_time` datetime DEFAULT NULL COMMENT '更新時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
(4) 建立Job工廠類
import com.tools.SpringUtil; import org.quartz.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @CreateTime: 2019-05-20 15:07 * @Description: 任務執行器 * @Author: WH */ public class SystemJobFactory implements Job { Logger log = LoggerFactory.getLogger(getClass()); @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobDetail jobDetail = jobExecutionContext.getJobDetail(); log.debug(jobDetail.getKey().getName()+"="+jobDetail.getKey().getGroup()); JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap(); //具體處理類組件名(處理類上加了@Component()注解,注意默認首字母數據庫存的時候是小寫) String className = jobDataMap.getString("className"); //具體處理類的方法名 String methodName = jobDataMap.getString("methodName"); //獲取對應的Bean Object object = SpringUtil.getBean(className); try { //利用反射執行對應方法 Method method = object.getClass().getMethod(methodName); method.invoke(object); } catch (Exception e) { e.printStackTrace(); } } }
(5) 建立任務Trigger觸發器監聽類
import org.quartz.JobExecutionContext; import org.quartz.Trigger; import org.quartz.TriggerListener; /** * @CreateTime: 2019-05-20 16:13 * @Description: 任務監聽 * @Author: WH */ public class SystemJobTriggerListener implements TriggerListener { @Override public String getName() { return this.getClass().getSimpleName(); } @Override public void triggerFired(Trigger trigger, JobExecutionContext jobExecutionContext) { } @Override public boolean vetoJobExecution(Trigger trigger, JobExecutionContext jobExecutionContext) { return false; } @Override public void triggerMisfired(Trigger trigger) { } @Override public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) { } }
(6) 建立業務控制處理類
private Boolean startTask(SysScheduleInfo sysScheduleInfo){ boolean ret = true; try { //創建觸發器 Trigger trigger = TriggerBuilder.newTrigger().withIdentity(sysScheduleInfo.getTriggerName(),sysScheduleInfo.getTaskGroup()) .withSchedule(CronScheduleBuilder.cronSchedule(sysScheduleInfo.getTriggerCronExpression()).withMisfireHandlingInstructionDoNothing()) .startNow() .build(); //創建任務 JobDetail jobDetail = JobBuilder.newJob(SystemJobFactory.class) .withIdentity(sysScheduleInfo.getTaskName(),sysScheduleInfo.getTaskGroup()) .usingJobData("className",sysScheduleInfo.getExecuteClassName()) .usingJobData("methodName",sysScheduleInfo.getExecuteMethodName()) .build(); //調度作業 scheduler.scheduleJob(jobDetail, trigger); //添加監聽 ListenerManager listenerManager = scheduler.getListenerManager(); TriggerListener systemJobTriggerListener = listenerManager.getTriggerListener("systemJobTriggerListener"); if(systemJobTriggerListener==null){ listenerManager.addTriggerListener(new SystemJobTriggerListener(), EverythingMatcher.allTriggers()); } } catch (SchedulerException e) { ret = false; e.printStackTrace(); } return ret; } private Boolean deleteTask(SysScheduleInfo sysScheduleInfo){ boolean ret = true; try { //觸發器標識 TriggerKey triggerKey = TriggerKey.triggerKey(sysScheduleInfo.getTriggerName(),sysScheduleInfo.getTriggerGroup()); //任務標識 JobKey jobKey = JobKey.jobKey(sysScheduleInfo.getTaskName(),sysScheduleInfo.getTaskGroup()); //停止任務 scheduler.pauseJob(jobKey); //停止觸發器 scheduler.pauseTrigger(triggerKey); //移除觸發器 scheduler.unscheduleJob(triggerKey); //刪除任務 scheduler.deleteJob(jobKey); } catch (SchedulerException e) { ret = false; e.printStackTrace(); } return ret; }
(7) 項目重啟重置任務處理
import com.base.taskmanager.bean.SysScheduleInfo; import com.base.taskmanager.bean.SystemJobFactory; import com.base.taskmanager.bean.SystemJobTriggerListener; import com.base.taskmanager.dao.SysScheduleInfoMapper; import org.quartz.*; import org.quartz.impl.matchers.EverythingMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; /** * @CreateTime: 2019-05-21 09:12 * @Description: 項目初始化-在springboot的最后一步執行 * @Author: WH */ @Component public class DataCenerInitialization implements CommandLineRunner { Logger log = LoggerFactory.getLogger(getClass()); @Autowired private SysScheduleInfoMapper sysScheduleInfoMapper; @Autowired private Scheduler scheduler; @Override public void run(String... args) throws Exception { //項目重新啟動的時候從新加載任務數據 List<SysScheduleInfo> sysScheduleInfos = sysScheduleInfoMapper.selectEffectiveData();//狀態1,並且允許啟動的數據 //添加監聽 ListenerManager listenerManager = scheduler.getListenerManager(); TriggerListener systemJobTriggerListener = listenerManager.getTriggerListener("systemJobTriggerListener"); if(systemJobTriggerListener==null){ listenerManager.addTriggerListener(new SystemJobTriggerListener(), EverythingMatcher.allTriggers()); } //終止之前的所有任務 int start = 0; for (SysScheduleInfo sysScheduleInfo : sysScheduleInfos) { //觸發器標識 TriggerKey triggerKey = TriggerKey.triggerKey(sysScheduleInfo.getTriggerName(),sysScheduleInfo.getTriggerGroup()); //任務標識 JobKey jobKey = JobKey.jobKey(sysScheduleInfo.getTaskName(),sysScheduleInfo.getTaskGroup()); //停止任務 scheduler.pauseJob(jobKey); //停止觸發器 scheduler.pauseTrigger(triggerKey); //移除觸發器 scheduler.unscheduleJob(triggerKey); //刪除任務 scheduler.deleteJob(jobKey); start++; } //從新加載當前任務 int end = 0; for (SysScheduleInfo sysScheduleInfo : sysScheduleInfos) { try { //創建觸發器 Trigger trigger = TriggerBuilder.newTrigger().withIdentity(sysScheduleInfo.getTriggerName(),sysScheduleInfo.getTaskGroup()) .withSchedule(CronScheduleBuilder.cronSchedule(sysScheduleInfo.getTriggerCronExpression()).withMisfireHandlingInstructionDoNothing()) .startAt(date) .build(); //創建任務 JobDetail jobDetail = JobBuilder.newJob(SystemJobFactory.class) .withIdentity(sysScheduleInfo.getTaskName(),sysScheduleInfo.getTaskGroup()) .usingJobData("className",sysScheduleInfo.getExecuteClassName()) .usingJobData("methodName",sysScheduleInfo.getExecuteMethodName()) .build(); //調度作業 scheduler.scheduleJob(jobDetail, trigger); end++; } catch (SchedulerException e) { e.printStackTrace(); } } } }
(8) 配置quartz參數
spring: profiles: dev quartz: job-store-type: jdbc #數據庫方式 jdbc: initialize-schema: never #不初始化表結構 properties: org: quartz: scheduler: instanceId: AUTO #默認主機名和時間戳生成實例ID,可以是任何字符串,但對於所有調度程序來說,必須是唯一的 對應qrtz_scheduler_state INSTANCE_NAME字段 instanceName: clusteredScheduler #quartzScheduler jobStore: acquireTriggersWithinLock: true class: org.quartz.impl.jdbcjobstore.JobStoreTX #持久化配置 driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #我們僅為數據庫制作了特定於數據庫的代理 useProperties: true #以指示JDBCJobStore將JobDataMaps中的所有值都作為字符串,因此可以作為名稱 - 值對存儲而不是在BLOB列中以其序列化形式存儲更多復雜的對象。從長遠來看,這是更安全的,因為您避免了將非String類序列化為BLOB的類版本問題。 tablePrefix: qrtz_ #數據庫表前綴 misfireThreshold: 60000 #在被認為“失火”之前,調度程序將“容忍”一個Triggers將其下一個啟動時間通過的毫秒數。默認值(如果您在配置中未輸入此屬性)為60000(60秒)。 clusterCheckinInterval: 5000 #設置此實例“檢入”*與群集的其他實例的頻率(以毫秒為單位)。影響檢測失敗實例的速度。 isClustered: false #打開群集功能,集群模式需要在多台服務器上做時間同步或者使用zookeeper去解決 threadPool: #連接池 class: org.quartz.simpl.SimpleThreadPool threadCount: 10 threadPriority: 5 threadsInheritContextClassLoaderOfInitializingThread: true startup-delay: 30 overwrite-existing-jobs: true
三. 目前的認識(個人理解)
因為是單服務器項目其實現在並不涉及集群部署,但是自己想嘗試下動態配置quartz的感覺,所有就這幾天復習了下基礎,隨便搭建了一個可以動態調度任務的簡單服務出來;
在做的時候越遇到了一些坑,比如停止服務后重啟服務時會重新執行停止期間的任務,動態調用直接使用quartz的sql表進行處理很難,需要建立中間表進行處理,對於整個quartz而已無非就是開啟任務和關閉任務等;
關閉任務 = 移除任務 --> delete
暫停任務 = 移除任務 --> delete
開啟任務 = 新建任務添加到調度中 --> add
停止服務器后,重啟的時候直接移除調度隊列中的所有任務,從新在數據庫中間表把任務從新添加進去(初始化);
重啟服務器后重啟的問題,給服務增加延遲啟動時間,在啟動好在初始化數據庫中間表中的任務,這樣就不會啟動過程中去執行停止服務期間遺漏的任務了;
多個CommandLineRunner可以被同時執行在同一個spring上下文中並且執行順序是以order注解的參數順序一致,用於初始化