quartz - springboot2.1.5 整合


一. 整合的步驟

  • 建立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注解的參數順序一致,用於初始化


免責聲明!

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



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