<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          任務(wù)調(diào)度框架 Quartz 用法指南「超詳細(xì)」

          共 45306字,需瀏覽 91分鐘

           ·

          2022-05-27 14:14

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

          大家好,我是胖虎!

          今天早上,見交流群的小伙伴兒正在熱火朝天的討論著各種實現(xiàn)自定義定時任務(wù)的方案,從Quartz到Xxl-job,再到Elastic-job,能聊的都聊了一圈兒;剛剛好手頭有一份關(guān)于 Quartz 的保姆級教程,在這里分享給大家;

          1前言

          項目中遇到一個,需要 客戶自定任務(wù)啟動時間 的需求。原來一直都是在項目里硬編碼一些定時器,所以沒有學(xué)習(xí)過。

          很多開源的項目管理框架都已經(jīng)做了 Quartz 的集成。我們居然連這么常用得東西居然沒有做成模塊化,實在是不應(yīng)該。

          Quartz是OpenSymphony開源組織在Job scheduling領(lǐng)域又一個開源項目,完全由Java開發(fā),可以用來執(zhí)行定時任務(wù),類似于java.util.Timer。但是相較于Timer, Quartz增加了很多功能:

          • 持久性作業(yè) - 就是保持調(diào)度定時的狀態(tài);
          • 作業(yè)管理 - 對調(diào)度作業(yè)進(jìn)行有效的管理;

          官方文檔:

          • http://www.quartz-scheduler.org/documentation/
          • http://www.quartz-scheduler.org/api/2.3.0/index.html

          2基礎(chǔ)使用

          Quartz 的核心類有以下三部分:

          • 任務(wù) Job : 需要實現(xiàn)的任務(wù)類,實現(xiàn) execute() 方法,執(zhí)行后完成任務(wù)。
          • 觸發(fā)器 Trigger : 包括 SimpleTriggerCronTrigger
          • 調(diào)度器 Scheduler : 任務(wù)調(diào)度器,負(fù)責(zé)基于 Trigger觸發(fā)器,來執(zhí)行 Job任務(wù)。

          主要關(guān)系如下:

          3Demo

          按照官網(wǎng)的 Demo,搭建一個純 maven 項目,添加依賴:

          <!-- 核心包 -->
          <dependency>
              <groupId>org.quartz-scheduler</groupId>
              <artifactId>quartz</artifactId>
              <version>2.3.0</version>
          </dependency>
          <!-- 工具包 -->
          <dependency>
              <groupId>org.quartz-scheduler</groupId>
              <artifactId>quartz-jobs</artifactId>
              <version>2.3.0</version>
          </dependency>

          新建一個任務(wù),實現(xiàn)了 org.quartz.Job 接口:

          public class MyJob implements Job {
              @Override
              public void execute(JobExecutionContext context) throws JobExecutionException {
                  System.out.println("任務(wù)被執(zhí)行了。。。");
              }
          }

          main 方法,創(chuàng)建調(diào)度器、jobDetail 實例、trigger 實例、執(zhí)行:

          public static void main(String[] args) throws Exception {
              // 1.創(chuàng)建調(diào)度器 Scheduler
              SchedulerFactory factory = new StdSchedulerFactory();
              Scheduler scheduler = factory.getScheduler();

              // 2.創(chuàng)建JobDetail實例,并與MyJob類綁定(Job執(zhí)行內(nèi)容)
              JobDetail job = JobBuilder.newJob(MyJob.class)
                  .withIdentity("job1", "group1")
                  .build()
          ;

              // 3.構(gòu)建Trigger實例,每隔30s執(zhí)行一次
              Trigger trigger = TriggerBuilder.newTrigger()
                  .withIdentity("trigger1""group1")
                  .startNow()
                  .withSchedule(simpleSchedule()
                                .withIntervalInSeconds(30)
                                .repeatForever())
                  .build();

              // 4.執(zhí)行,開啟調(diào)度器
              scheduler.scheduleJob(job, trigger);
              System.out.println(System.currentTimeMillis());
              scheduler.start();

              //主線程睡眠1分鐘,然后關(guān)閉調(diào)度器
              TimeUnit.MINUTES.sleep(1);
              scheduler.shutdown();
              System.out.println(System.currentTimeMillis());
          }

          日志打印情況:

          4JobDetail

          JobDetail 的作用是綁定 Job,是一個任務(wù)實例,它為 Job 添加了許多擴(kuò)展參數(shù)。

          每次Scheduler調(diào)度執(zhí)行一個Job的時候,首先會拿到對應(yīng)的Job,然后創(chuàng)建該Job實例,再去執(zhí)行Job中的execute()的內(nèi)容,任務(wù)執(zhí)行結(jié)束后,關(guān)聯(lián)的Job對象實例會被釋放,且會被JVM GC清除。

          為什么設(shè)計成JobDetail + Job,不直接使用Job?

          JobDetail 定義的是任務(wù)數(shù)據(jù),而真正的執(zhí)行邏輯是在Job中。

          這是因為任務(wù)是有可能并發(fā)執(zhí)行,如果Scheduler直接使用Job,就會存在對同一個Job實例并發(fā)訪問的問題。

          JobDetail & Job 方式,Sheduler每次執(zhí)行,都會根據(jù)JobDetail創(chuàng)建一個新的Job實例,這樣就可以 規(guī)避并發(fā)訪問 的問題。

          5JobExecutionContext

          • 當(dāng) Scheduler 調(diào)用一個 job,就會將 JobExecutionContext 傳遞給 Job 的 execute() 方法;
          • Job 能通過 JobExecutionContext 對象訪問到 Quartz 運行時候的環(huán)境以及 Job 本身的明細(xì)數(shù)據(jù)。

          任務(wù)實現(xiàn)的 execute() 方法,可以通過 context 參數(shù)獲取。

          public interface Job {
              void execute(JobExecutionContext context)
                  throws JobExecutionException
          ;
          }

          Builder 建造過程中,可以使用如下方法:

          usingJobData("tiggerDataMap""測試傳參")

          execute 方法中獲取:

          context.getTrigger().getJobDataMap().get("tiggerDataMap");
          context.getJobDetail().getJobDataMap().get("tiggerDataMap");

          6Job 狀態(tài)參數(shù)

          有狀態(tài)的 job 可以理解為多次 job調(diào)用期間可以持有一些狀態(tài)信息,這些狀態(tài)信息存儲在 JobDataMap 中。

          而默認(rèn)的無狀態(tài) job,每次調(diào)用時都會創(chuàng)建一個新的 JobDataMap

          示例如下:

          //多次調(diào)用 Job 的時候,將參數(shù)保留在 JobDataMap
          @PersistJobDataAfterExecution
          public class JobStatus implements Job {
              @Override
              public void execute(JobExecutionContext context) throws JobExecutionException {
                  long count = (long) context.getJobDetail().getJobDataMap().get("count");
                  System.out.println("當(dāng)前執(zhí)行,第" + count + "次");
                  context.getJobDetail().getJobDataMap().put("count", ++count);
              }
          }
          JobDetail job = JobBuilder.newJob(JobStatus.class)
                          .withIdentity("statusJob", "group1")
                          .usingJobData("count", 1L)
                          .build()
          ;

          輸出結(jié)果:

          當(dāng)前執(zhí)行,第1
          [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
          當(dāng)前執(zhí)行,第2
          當(dāng)前執(zhí)行,第3

          7Trigger

          定時啟動/關(guān)閉

          Trigger 可以設(shè)置任務(wù)的開始結(jié)束時間, Scheduler 會根據(jù)參數(shù)進(jìn)行觸發(fā)。

          Calendar instance = Calendar.getInstance();
          Date startTime = instance.getTime();
          instance.add(Calendar.MINUTE, 1);
          Date endTime = instance.getTime();

          // 3.構(gòu)建Trigger實例
          Trigger trigger = TriggerBuilder.newTrigger()
              .withIdentity("trigger1""group1")
              // 開始時間
              .startAt(startTime)
              // 結(jié)束時間
              .endAt(endTime)
              .build();

          在 job 中也能拿到對應(yīng)的時間,并進(jìn)行業(yè)務(wù)判斷

          public void execute(JobExecutionContext context) throws JobExecutionException {
              System.out.println("任務(wù)執(zhí)行。。。");
              System.out.println(context.getTrigger().getStartTime());
              System.out.println(context.getTrigger().getEndTime());
          }

          運行結(jié)果:

          [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.0
          1633149326723
          任務(wù)執(zhí)行。。。
          Sat Oct 02 12:35:26 CST 2021
          Sat Oct 02 12:36:26 CST 2021
          [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.

          8SimpleTrigger

          這是比較簡單的一類觸發(fā)器,用它能實現(xiàn)很多基礎(chǔ)的應(yīng)用。使用它的主要場景包括:

          • 在指定時間段內(nèi),執(zhí)行一次任務(wù)

            最基礎(chǔ)的 Trigger 不設(shè)置循環(huán),設(shè)置開始時間。

          • 在指定時間段內(nèi),循環(huán)執(zhí)行任務(wù)

            在 1 基礎(chǔ)上加上循環(huán)間隔。可以指定 永遠(yuǎn)循環(huán)、運行指定次數(shù)

            TriggerBuilder.newTrigger()
                .withSchedule(SimpleScheduleBuilder
                              .simpleSchedule()
                              .withIntervalInSeconds(30)
                              .repeatForever())

            withRepeatCount(count) 是重復(fù)次數(shù),實際運行次數(shù)為 count+1

            TriggerBuilder.newTrigger()
                .withSchedule(SimpleScheduleBuilder
                              .simpleSchedule()
                              .withIntervalInSeconds(30)
                              .withRepeatCount(5))
          • 立即開始,指定時間結(jié)束

            這個,略。

          9CronTrigger

          CronTrigger 是基于日歷的任務(wù)調(diào)度器,在實際應(yīng)用中更加常用。

          雖然很常用,但是知識點都一樣,只是可以通過表達(dá)式來設(shè)置時間而已。

          使用方式就是綁定調(diào)度器時換一下:

          TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))

          Cron 表達(dá)式這里不介紹,貼個圖跳過

          順便推薦一個非常好用的Cron 表達(dá)式在線生成,反解析的工具:www.matools.com/cron 非常好用,點幾下,就能得到自己想要的cron表達(dá)式;

          10SpringBoot 整合

          下面集成應(yīng)用截圖來自 Ruoyi 框架:

          從上面的截圖中,可以看到這個定時任務(wù)模塊實現(xiàn)了:

          • cron表達(dá)式定時執(zhí)行
          • 并發(fā)執(zhí)行
          • 錯誤策略
          • 啟動執(zhí)行、暫停執(zhí)行

          如果再加上:設(shè)置啟動時間、停止時間 就更好了。不過啟停時間只是調(diào)用兩個方法而已,也就不寫了。

          這一部分就主要是看 RuoYi 框架 代碼,然后加一點我需要用的功能。

          前端部分就不寫了,全部用 swagger 代替,一些基礎(chǔ)字段也刪除了,僅復(fù)制主體功能。

          已完成代碼示例:

          https://gitee.com/qianwei4712/code-of-shiva/tree/master/quartz

          11環(huán)境準(zhǔn)備

          從 springboot 2.4.10 開始,添加 quartz 的 maven 依賴:

          <!--  Quartz 任務(wù)調(diào)度 -->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-quartz</artifactId>
          </dependency>

          application 配置文件:

          # 開發(fā)環(huán)境配置
          server:
            # 服務(wù)器的HTTP端口
            port: 80
            servlet:
              # 應(yīng)用的訪問路徑
              context-path: /
            tomcat:
              # tomcat的URI編碼
              uri-encoding: UTF-8

          spring:
            datasource:
              username: root
              password: root
              url: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=true
              driver-class-name: com.mysql.cj.jdbc.Driver

              # HikariPool 較佳配置
              hikari:
                # 客戶端(即您)等待來自池的連接的最大毫秒數(shù)
                connection-timeout: 60000
                # 控制將測試連接的活動性的最長時間
                validation-timeout: 3000
                # 控制允許連接在池中保持空閑狀態(tài)的最長時間
                idle-timeout: 60000

                login-timeout: 5
                # 控制池中連接的最大生存期
                max-lifetime: 60000
                # 控制允許池達(dá)到的最大大小,包括空閑和使用中的連接
                maximum-pool-size: 10
                # 控制HikariCP嘗試在池中維護(hù)的最小空閑連接數(shù)
                minimum-idle: 10
                # 控制默認(rèn)情況下從池獲得的連接是否處于只讀模式
                read-only: false

          Quartz 自帶有數(shù)據(jù)庫模式,腳本都是現(xiàn)成的:

          下載這個腳本:

          https://gitee.com/qianwei4712/code-of-shiva/blob/master/quartz/quartz.sql

          保存任務(wù)的數(shù)據(jù)庫表:

          CREATE TABLE `quartz_job` (
            `job_id` bigint(20NOT NULL AUTO_INCREMENT COMMENT '任務(wù)ID',
            `job_name` varchar(64NOT NULL DEFAULT '' COMMENT '任務(wù)名稱',
            `job_group` varchar(64NOT NULL DEFAULT 'DEFAULT' COMMENT '任務(wù)組名',
            `invoke_target` varchar(500NOT NULL COMMENT '調(diào)用目標(biāo)字符串',
            `cron_expression` varchar(255DEFAULT '' COMMENT 'cron執(zhí)行表達(dá)式',
            `misfire_policy` varchar(20DEFAULT '3' COMMENT '計劃執(zhí)行錯誤策略(1立即執(zhí)行 2執(zhí)行一次 3放棄執(zhí)行)',
            `concurrent` char(1DEFAULT '1' COMMENT '是否并發(fā)執(zhí)行(0允許 1禁止)',
            `status` char(1DEFAULT '0' COMMENT '狀態(tài)(0正常 1暫停)',
            `remark` varchar(500DEFAULT '' COMMENT '備注信息',
            PRIMARY KEY (`job_id`,`job_name`,`job_group`)
          ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='定時任務(wù)調(diào)度表';

          最后準(zhǔn)備一個任務(wù)方法:

          @Slf4j
          @Component("mysqlJob")
          public class MysqlJob {
              protected final Logger logger = LoggerFactory.getLogger(this.getClass());
              public void execute(String param) {
                  logger.info("執(zhí)行 Mysql Job,當(dāng)前時間:{},任務(wù)參數(shù):{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), param);
              }
          }

          12核心代碼

          ScheduleConfig 配置代碼類:

          @Configuration
          public class ScheduleConfig {

              @Bean
              public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
                  SchedulerFactoryBean factory = new SchedulerFactoryBean();
                  factory.setDataSource(dataSource);

                  // quartz參數(shù)
                  Properties prop = new Properties();
                  prop.put("org.quartz.scheduler.instanceName""shivaScheduler");
                  prop.put("org.quartz.scheduler.instanceId""AUTO");
                  // 線程池配置
                  prop.put("org.quartz.threadPool.class""org.quartz.simpl.SimpleThreadPool");
                  prop.put("org.quartz.threadPool.threadCount""20");
                  prop.put("org.quartz.threadPool.threadPriority""5");
                  // JobStore配置
                  prop.put("org.quartz.jobStore.class""org.quartz.impl.jdbcjobstore.JobStoreTX");
                  // 集群配置
                  prop.put("org.quartz.jobStore.isClustered""true");
                  prop.put("org.quartz.jobStore.clusterCheckinInterval""15000");
                  prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime""1");
                  prop.put("org.quartz.jobStore.txIsolationLevelSerializable""true");

                  // sqlserver 啟用
                  // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
                  prop.put("org.quartz.jobStore.misfireThreshold""12000");
                  prop.put("org.quartz.jobStore.tablePrefix""QRTZ_");
                  factory.setQuartzProperties(prop);

                  factory.setSchedulerName("shivaScheduler");
                  // 延時啟動
                  factory.setStartupDelay(1);
                  factory.setApplicationContextSchedulerContextKey("applicationContextKey");
                  // 可選,QuartzScheduler
                  // 啟動時更新己存在的Job,這樣就不用每次修改targetObject后刪除qrtz_job_details表對應(yīng)記錄了
                  factory.setOverwriteExistingJobs(true);
                  // 設(shè)置自動啟動,默認(rèn)為true
                  factory.setAutoStartup(true);

                  return factory;
              }
          }

          ScheduleUtils 調(diào)度工具類,這是本篇中最核心的代碼:

          public class ScheduleUtils {
              /**
               * 得到quartz任務(wù)類
               *
               * @param job 執(zhí)行計劃
               * @return 具體執(zhí)行任務(wù)類
               */

              private static Class<? extends Job> getQuartzJobClass(QuartzJob job) {
                  boolean isConcurrent = "0".equals(job.getConcurrent());
                  return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
              }

              /**
               * 構(gòu)建任務(wù)觸發(fā)對象
               */

              public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
                  return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
              }

              /**
               * 構(gòu)建任務(wù)鍵對象
               */

              public static JobKey getJobKey(Long jobId, String jobGroup) {
                  return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
              }

              /**
               * 創(chuàng)建定時任務(wù)
               */

              public static void createScheduleJob(Scheduler scheduler, QuartzJob job) throws Exception {
                  Class<? extends Job> jobClass = getQuartzJobClass(job);
                  // 構(gòu)建job信息
                  Long jobId = job.getJobId();
                  String jobGroup = job.getJobGroup();
                  JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();

                  // 表達(dá)式調(diào)度構(gòu)建器
                  CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
                  cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);

                  // 按新的cronExpression表達(dá)式構(gòu)建一個新的trigger
                  CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
                          .withSchedule(cronScheduleBuilder).build();

                  // 放入?yún)?shù),運行時的方法可以獲取
                  jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);

                  // 判斷是否存在
                  if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
                      // 防止創(chuàng)建時存在數(shù)據(jù)問題 先移除,然后在執(zhí)行創(chuàng)建操作
                      scheduler.deleteJob(getJobKey(jobId, jobGroup));
                  }

                  scheduler.scheduleJob(jobDetail, trigger);

                  // 暫停任務(wù)
                  if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
                      scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
                  }
              }

              /**
               * 設(shè)置定時任務(wù)策略
               */

              public static CronScheduleBuilder handleCronScheduleMisfirePolicy(QuartzJob job, CronScheduleBuilder cb)
                      throws Exception 
          {
                  switch (job.getMisfirePolicy()) {
                      case ScheduleConstants.MISFIRE_DEFAULT:
                          return cb;
                      case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                          return cb.withMisfireHandlingInstructionIgnoreMisfires();
                      case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                          return cb.withMisfireHandlingInstructionFireAndProceed();
                      case ScheduleConstants.MISFIRE_DO_NOTHING:
                          return cb.withMisfireHandlingInstructionDoNothing();
                      default:
                          throw new Exception("The task misfire policy '" + job.getMisfirePolicy()
                                  + "' cannot be used in cron schedule tasks");
                  }
              }
          }

          這里可以看到,在完成任務(wù)與觸發(fā)器的關(guān)聯(lián)后,如果是暫停狀態(tài),會先讓調(diào)度器停止任務(wù)。

          AbstractQuartzJob 抽象任務(wù):

          public abstract class AbstractQuartzJob implements Job {
              private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);

              /**
               * 線程本地變量
               */

              private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();

              @Override
              public void execute(JobExecutionContext context) throws JobExecutionException {
                  QuartzJob job = new QuartzJob();
                  BeanUtils.copyBeanProp(job, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
                  try {
                      before(context, job);
                      if (job != null) {
                          doExecute(context, job);
                      }
                      after(context, job, null);
                  } catch (Exception e) {
                      log.error("任務(wù)執(zhí)行異常  - :", e);
                      after(context, job, e);
                  }
              }

              /**
               * 執(zhí)行前
               *
               * @param context 工作執(zhí)行上下文對象
               * @param job     系統(tǒng)計劃任務(wù)
               */

              protected void before(JobExecutionContext context, QuartzJob job) {
                  threadLocal.set(new Date());
              }

              /**
               * 執(zhí)行后
               *
               * @param context 工作執(zhí)行上下文對象
               * @param sysJob  系統(tǒng)計劃任務(wù)
               */

              protected void after(JobExecutionContext context, QuartzJob sysJob, Exception e) {

              }

              /**
               * 執(zhí)行方法,由子類重載
               *
               * @param context 工作執(zhí)行上下文對象
               * @param job     系統(tǒng)計劃任務(wù)
               * @throws Exception 執(zhí)行過程中的異常
               */

              protected abstract void doExecute(JobExecutionContext context, QuartzJob job) throws Exception;
          }

          這個類將原本 execute 方法執(zhí)行的任務(wù),下放到了子類重載的 doExecute 方法中

          同時準(zhǔn)備實現(xiàn),分了允許并發(fā)和不允許并發(fā),差別就是一個注解:

          public class QuartzJobExecution extends AbstractQuartzJob {
              @Override
              protected void doExecute(JobExecutionContext context, QuartzJob job) throws Exception {
                  JobInvokeUtil.invokeMethod(job);
              }
          }
          @DisallowConcurrentExecution
          public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
              @Override
              protected void doExecute(JobExecutionContext context, QuartzJob job) throws Exception {
                  JobInvokeUtil.invokeMethod(job);
              }
          }

          最后由 JobInvokeUtil 通過反射,進(jìn)行實際的方法調(diào)用:

          public class JobInvokeUtil {
              /**
               * 執(zhí)行方法
               *
               * @param job 系統(tǒng)任務(wù)
               */

              public static void invokeMethod(QuartzJob job) throws Exception {
                  String invokeTarget = job.getInvokeTarget();
                  String beanName = getBeanName(invokeTarget);
                  String methodName = getMethodName(invokeTarget);
                  List<Object[]> methodParams = getMethodParams(invokeTarget);

                  if (!isValidClassName(beanName)) {
                      Object bean = SpringUtils.getBean(beanName);
                      invokeMethod(bean, methodName, methodParams);
                  } else {
                      Object bean = Class.forName(beanName).newInstance();
                      invokeMethod(bean, methodName, methodParams);
                  }
              }

              /**
               * 調(diào)用任務(wù)方法
               *
               * @param bean         目標(biāo)對象
               * @param methodName   方法名稱
               * @param methodParams 方法參數(shù)
               */

              private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
                      throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
                      InvocationTargetException 
          {
                  if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) {
                      Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
                      method.invoke(bean, getMethodParamsValue(methodParams));
                  } else {
                      Method method = bean.getClass().getDeclaredMethod(methodName);
                      method.invoke(bean);
                  }
              }

              /**
               * 校驗是否為為class包名
               *
               * @param invokeTarget 名稱
               * @return true是 false否
               */

              public static boolean isValidClassName(String invokeTarget) {
                  return StringUtils.countMatches(invokeTarget, ".") > 1;
              }

              /**
               * 獲取bean名稱
               *
               * @param invokeTarget 目標(biāo)字符串
               * @return bean名稱
               */

              public static String getBeanName(String invokeTarget) {
                  String beanName = StringUtils.substringBefore(invokeTarget, "(");
                  return StringUtils.substringBeforeLast(beanName, ".");
              }

              /**
               * 獲取bean方法
               *
               * @param invokeTarget 目標(biāo)字符串
               * @return method方法
               */

              public static String getMethodName(String invokeTarget) {
                  String methodName = StringUtils.substringBefore(invokeTarget, "(");
                  return StringUtils.substringAfterLast(methodName, ".");
              }

              /**
               * 獲取method方法參數(shù)相關(guān)列表
               *
               * @param invokeTarget 目標(biāo)字符串
               * @return method方法相關(guān)參數(shù)列表
               */

              public static List<Object[]> getMethodParams(String invokeTarget) {
                  String methodStr = StringUtils.substringBetween(invokeTarget, "("")");
                  if (StringUtils.isEmpty(methodStr)) {
                      return null;
                  }
                  String[] methodParams = methodStr.split(",");
                  List<Object[]> classs = new LinkedList<>();
                  for (int i = 0; i < methodParams.length; i++) {
                      String str = StringUtils.trimToEmpty(methodParams[i]);
                      // String字符串類型,包含'
                      if (StringUtils.contains(str, "'")) {
                          classs.add(new Object[]{StringUtils.replace(str, "'"""), String.class});
                      }
                      // boolean布爾類型,等于true或者false
                      else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) {
                          classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
                      }
                      // long長整形,包含L
                      else if (StringUtils.containsIgnoreCase(str, "L")) {
                          classs.add(new Object[]{Long.valueOf(StringUtils.replaceIgnoreCase(str, "L""")), Long.class});
                      }
                      // double浮點類型,包含D
                      else if (StringUtils.containsIgnoreCase(str, "D")) {
                          classs.add(new Object[]{Double.valueOf(StringUtils.replaceIgnoreCase(str, "D""")), Double.class});
                      }
                      // 其他類型歸類為整形
                      else {
                          classs.add(new Object[]{Integer.valueOf(str), Integer.class});
                      }
                  }
                  return classs;
              }

              /**
               * 獲取參數(shù)類型
               *
               * @param methodParams 參數(shù)相關(guān)列表
               * @return 參數(shù)類型列表
               */

              public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
                  Class<?>[] classs = new Class<?>[methodParams.size()];
                  int index = 0;
                  for (Object[] os : methodParams) {
                      classs[index] = (Class<?>) os[1];
                      index++;
                  }
                  return classs;
              }

              /**
               * 獲取參數(shù)值
               *
               * @param methodParams 參數(shù)相關(guān)列表
               * @return 參數(shù)值列表
               */

              public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
                  Object[] classs = new Object[methodParams.size()];
                  int index = 0;
                  for (Object[] os : methodParams) {
                      classs[index] = (Object) os[0];
                      index++;
                  }
                  return classs;
              }
          }

          啟動程序后可以看到,調(diào)度器已經(jīng)啟動:

          2021-10-06 16:26:05.162  INFO 10764 --- [shivaScheduler]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 1 seconds
          2021-10-06 16:26:05.306  INFO 10764 --- [shivaScheduler]] org.quartz.core.QuartzScheduler          : Scheduler shivaScheduler_$_DESKTOP-OKMJ1351633508761366 started.

          13新增調(diào)度任務(wù)

          添加任務(wù),使用如下 json 進(jìn)行請求:

          {
            "concurrent""1",
            "cronExpression""0/10 * * * * ?",
            "invokeTarget""mysqlJob.execute('got it!!!')",
            "jobGroup""mysqlGroup",
            "jobId"9,
            "jobName""新增 mysqlJob 任務(wù)",
            "misfirePolicy""1",
            "remark""",
            "status""0"
          }
          @Override
          @Transactional(rollbackFor = Exception.class)
          public int insertJob(QuartzJob jobthrows Exception 
          {
              // 先將任務(wù)設(shè)置為暫停狀態(tài)
              job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
              int rows = quartzMapper.insert(job);
              if (rows > 0) {
                  ScheduleUtils.createScheduleJob(scheduler, job);
              }
              return rows;
          }

          先將任務(wù)設(shè)置為暫停狀態(tài),數(shù)據(jù)庫插入成功后,在調(diào)度器創(chuàng)建任務(wù)。

          再手動啟動任務(wù),根據(jù) ID 來啟動任務(wù):

          實現(xiàn)代碼:

          @Override
              public int changeStatus(Long jobId, String status) throws SchedulerException {
                  int rows = quartzMapper.changeStatus(jobId, status);
                  if (rows == 0) {
                      return rows;
                  }
                  //更新成功,需要改調(diào)度器內(nèi)任務(wù)的狀態(tài)
                  //拿到整個任務(wù)
                  QuartzJob job = quartzMapper.selectJobById(jobId);
                  //根據(jù)狀態(tài)來啟動或者關(guān)閉
                  if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) {
                      rows = resumeJob(job);
                  } else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) {
                      rows = pauseJob(job);
                  }
                  return rows;
              }
          @Override
          public int resumeJob(QuartzJob job) throws SchedulerException {
              Long jobId = job.getJobId();
              String jobGroup = job.getJobGroup();
              job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
              int rows = quartzMapper.updateById(job);
              if (rows > 0) {
                  scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
              }
              return rows;
          }

          暫停任務(wù)的代碼也相同。

          調(diào)用啟動后可以看到控制臺打印日志:

          2021-10-06 20:36:30.018  INFO 8536 --- [eduler_Worker-3] cn.shiva.quartz.job.MysqlJob             : 執(zhí)行 Mysql Job,當(dāng)前時間:2021-10-06 20:36:30,任務(wù)參數(shù):got it!!!
          2021-10-06 20:36:40.016  INFO 8536 --- [eduler_Worker-4] cn.shiva.quartz.job.MysqlJob             : 執(zhí)行 Mysql Job,當(dāng)前時間:2021-10-06 20:36:40,任務(wù)參數(shù):got it!!!
          2021-10-06 20:36:50.017  INFO 8536 --- [eduler_Worker-5] cn.shiva.quartz.job.MysqlJob             : 執(zhí)行 Mysql Job,當(dāng)前時間:2021-10-06 20:36:50,任務(wù)參數(shù):got it!!!

          如果涉及到任務(wù)修改,需要在調(diào)度器先刪除原有任務(wù),重新創(chuàng)建調(diào)度任務(wù)。

          啟動初始化任務(wù)

          這部分倒是比較簡單,初始化的時候清空原有任務(wù),重新創(chuàng)建就好了:

          /**
            * 項目啟動時,初始化定時器 主要是防止手動修改數(shù)據(jù)庫導(dǎo)致未同步到定時任務(wù)處理(注:不能手動修改數(shù)據(jù)庫ID和任務(wù)組名,否則會導(dǎo)致臟數(shù)據(jù))
            */

          @PostConstruct
          public void init() throws Exception {
              scheduler.clear();
              List<QuartzJob> jobList = quartzMapper.selectJobAll();
              for (QuartzJob job : jobList) {
                  ScheduleUtils.createScheduleJob(scheduler, job);
              }
          }

          14其他說明

          并發(fā)執(zhí)行

          上面有并發(fā)和非并發(fā)的區(qū)別,通過 @DisallowConcurrentExecution 注解來實現(xiàn)阻止并發(fā)。

          Quartz定時任務(wù)默認(rèn)都是并發(fā)執(zhí)行的,不會等待上一次任務(wù)執(zhí)行完畢,只要間隔時間到就會執(zhí)行, 如果定時任執(zhí)行太長,會長時間占用資源,導(dǎo)致其它任務(wù)堵塞。

          @DisallowConcurrentExecution 禁止并發(fā)執(zhí)行多個相同定義的JobDetail, 這個注解是加在Job類上的, 但意思并不是不能同時執(zhí)行多個Job, 而是不能并發(fā)執(zhí)行同一個Job Definition(由JobDetail定義), 但是可以同時執(zhí)行多個不同的JobDetail。

          舉例說明,我們有一個Job類,叫做SayHelloJob, 并在這個Job上加了這個注解, 然后在這個Job上定義了很多個JobDetail, 如sayHelloToJoeJobDetail, sayHelloToMikeJobDetail, 那么當(dāng)scheduler啟動時, 不會并發(fā)執(zhí)行多個sayHelloToJoeJobDetail或者sayHelloToMikeJobDetail, 但可以同時執(zhí)行sayHelloToJoeJobDetailsayHelloToMikeJobDetail

          @PersistJobDataAfterExecution 同樣, 也是加在Job上。表示當(dāng)正常執(zhí)行完Job后, JobDataMap中的數(shù)據(jù)應(yīng)該被改動, 以被下一次調(diào)用時用。

          當(dāng)使用 @PersistJobDataAfterExecution 注解時, 為了避免并發(fā)時, 存儲數(shù)據(jù)造成混亂, 強(qiáng)烈建議把 @DisallowConcurrentExecution 注解也加上。

          測試代碼,設(shè)定的時間間隔為3秒,但job執(zhí)行時間是5秒,設(shè)置 @DisallowConcurrentExecution以 后程序會等任務(wù)執(zhí)行完畢以后再去執(zhí)行,否則會在3秒時再啟用新的線程執(zhí)行。

          阻止特定時間運行

          仍然是通過調(diào)度器實現(xiàn)的:

          //2014-8-15這一天不執(zhí)行任何任務(wù)
          Calendar c = new GregorianCalendar(2014715);
          cal.setDayExcluded(c, true);
          scheduler.addCalendar("exclude", cal, falsefalse);

          //...中間省略
          TriggerBuilder.newTrigger().modifiedByCalendar("exclude")....

          來源:https://blog.csdn.net/m0_46144826


          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點這里好文分享給更多人↓↓

          瀏覽 76
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月婷婷丁香五月 | 亚洲欧洲无码在线观看 | 伊人在线观看视频网站 | 日本道大香蕉伊人在线 | 国产午夜一区二区 |