它來了,任務(wù)調(diào)度框架 Quartz保姆級教程奉上
點擊上方藍色字體,選擇“設(shè)為星標”

前言
項目中遇到一個,需要 客戶自定任務(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 : 包括 SimpleTrigger和CronTrigger。調(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?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();
????????//?表達式調(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啟動程序后可以看到,調(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í)行sayHelloToJoeJobDetail跟sayHelloToMikeJobDetail
@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ù)群。暗號:加群
