<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保姆級教程奉上

          共 9427字,需瀏覽 19分鐘

           ·

          2022-05-28 11:45

          點擊上方藍色字體,選擇“設(shè)為星標”


          回復(fù)”學(xué)習(xí)資料“獲取學(xué)習(xí)寶典

          前言

          項目中遇到一個,需要 客戶自定任務(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è)進行有效的管理;

          官方文檔:

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

          基礎(chǔ)使用

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

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

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

          Demo

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


          <dependency>
          ????<groupId>org.quartz-schedulergroupId>
          ????<artifactId>quartzartifactId>
          ????<version>2.3.0version>
          dependency>

          <dependency>
          ????<groupId>org.quartz-schedulergroupId>
          ????<artifactId>quartz-jobsartifactId>
          ????<version>2.3.0version>
          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());
          }

          日志打印情況:

          JobDetail

          JobDetail 的作用是綁定 Job,是一個任務(wù)實例,它為 Job 添加了許多擴展參數(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ā)訪問 的問題。

          JobExecutionContext

          • Scheduler 調(diào)用一個 job,就會將 JobExecutionContext 傳遞給 Job 的 execute() 方法;
          • Job 能通過 JobExecutionContext 對象訪問到 Quartz 運行時候的環(huán)境以及 Job 本身的明細數(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");

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

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

          而默認的無狀態(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("當前執(zhí)行,第"?+?count?+?"次");
          ????????context.getJobDetail().getJobDataMap().put("count",?++count);
          ????}
          }
          JobDetail?job?=?JobBuilder.newJob(JobStatus.class)
          ????????????????.withIdentity("statusJob",?"group1")
          ????????????????.usingJobData("count",?1L)
          ????????????????.build()
          ;

          輸出結(jié)果:

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

          Trigger

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

          Trigger 可以設(shè)置任務(wù)的開始結(jié)束時間, Scheduler 會根據(jù)參數(shù)進行觸發(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)的時間,并進行業(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.

          SimpleTrigger

          這是比較簡單的一類觸發(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)間隔。可以指定 永遠循環(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é)束

          這個,略。

          CronTrigger

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

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

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

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

          Cron 表達式這里不介紹,貼個圖跳過

          SpringBoot 整合

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

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

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

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

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

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

          已完成代碼示例:

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

          環(huán)境準備

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


          <dependency>
          ????<groupId>org.springframework.bootgroupId>
          ????<artifactId>spring-boot-starter-quartzartifactId>
          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
          ??????#?控制允許池達到的最大大小,包括空閑和使用中的連接
          ??????maximum-pool-size:?10
          ??????#?控制HikariCP嘗試在池中維護的最小空閑連接數(shù)
          ??????minimum-idle:?10
          ??????#?控制默認情況下從池獲得的連接是否處于只讀模式
          ??????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(20)?NOT?NULL?AUTO_INCREMENT?COMMENT?'任務(wù)ID',
          ??`job_name`?varchar(64)?NOT?NULL?DEFAULT?''?COMMENT?'任務(wù)名稱',
          ??`job_group`?varchar(64)?NOT?NULL?DEFAULT?'DEFAULT'?COMMENT?'任務(wù)組名',
          ??`invoke_target`?varchar(500)?NOT?NULL?COMMENT?'調(diào)用目標字符串',
          ??`cron_expression`?varchar(255)?DEFAULT?''?COMMENT?'cron執(zhí)行表達式',
          ??`misfire_policy`?varchar(20)?DEFAULT?'3'?COMMENT?'計劃執(zhí)行錯誤策略(1立即執(zhí)行?2執(zhí)行一次?3放棄執(zhí)行)',
          ??`concurrent`?char(1)?DEFAULT?'1'?COMMENT?'是否并發(fā)執(zhí)行(0允許?1禁止)',
          ??`status`?char(1)?DEFAULT?'0'?COMMENT?'狀態(tài)(0正常?1暫停)',
          ??`remark`?varchar(500)?DEFAULT?''?COMMENT?'備注信息',
          ??PRIMARY?KEY?(`job_id`,`job_name`,`job_group`)
          )?ENGINE=InnoDB?AUTO_INCREMENT=2?DEFAULT?CHARSET=utf8?COMMENT='定時任務(wù)調(diào)度表';

          最后準備一個任務(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,當前時間:{},任務(wù)參數(shù):{}",?LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd?HH:mm:ss")),?param);
          ????}
          }

          核心代碼

          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è)置自動啟動,默認為true
          ????????factory.setAutoStartup(true);

          ????????return?factory;
          ????}
          }

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

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

          ????private?static?Class?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?jobClass?=?getQuartzJobClass(job);
          ????????//?構(gòu)建job信息
          ????????Long?jobId?=?job.getJobId();
          ????????String?jobGroup?=?job.getJobGroup();
          ????????JobDetail?jobDetail?=?JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId,?jobGroup)).build();

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

          ????????//?按新的cronExpression表達式構(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?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 方法中

          同時準備實現(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 通過反射,進行實際的方法調(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?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?????????目標對象
          ?????*?@param?methodName???方法名稱
          ?????*?@param?methodParams?方法參數(shù)
          ?????*/

          ????private?static?void?invokeMethod(Object?bean,?String?methodName,?List?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?目標字符串
          ?????*?@return?bean名稱
          ?????*/

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

          ????/**
          ?????*?獲取bean方法
          ?????*
          ?????*?@param?invokeTarget?目標字符串
          ?????*?@return?method方法
          ?????*/

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

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

          ????public?static?List?getMethodParams(String?invokeTarget)?{
          ????????String?methodStr?=?StringUtils.substringBetween(invokeTarget,?"(",?")");
          ????????if?(StringUtils.isEmpty(methodStr))?{
          ????????????return?null;
          ????????}
          ????????String[]?methodParams?=?methodStr.split(",");
          ????????List?classs?=?new?LinkedList<>();
          ????????for?(int?i?=?0;?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?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?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.

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

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

          {
          ??"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?job)?throws?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,當前時間: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,當前時間: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,當前時間: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?jobList?=?quartzMapper.selectJobAll();
          ????for?(QuartzJob?job?:?jobList)?{
          ????????ScheduleUtils.createScheduleJob(scheduler,?job);
          ????}
          }

          其他說明

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

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

          Quartz定時任務(wù)默認都是并發(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, 那么當scheduler啟動時, 不會并發(fā)執(zhí)行多個sayHelloToJoeJobDetail或者sayHelloToMikeJobDetail, 但可以同時執(zhí)行sayHelloToJoeJobDetailsayHelloToMikeJobDetail

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

          當使用 @PersistJobDataAfterExecution 注解時, 為了避免并發(fā)時, 存儲數(shù)據(jù)造成混亂, 強烈建議把 @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(2014,?7,?15);
          cal.setDayExcluded(c,?true);
          scheduler.addCalendar("exclude",?cal,?false,?false);

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

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


          -------------? END??-------------
          掃描下方二維碼,加入技術(shù)群。暗號:加群


          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  69国产成年A片免费观看软件 | 夫妻自拍在线观看 | 五月天欧美色图 | 西西人体444WWW无码男男 | 日本岛国视频在线观看一区二区三区 |