諸位讀者都知道筆者寫東西都是用到才寫,筆者的學(xué)習(xí)足跡自從參加工作之后就是 非系統(tǒng) 學(xué)習(xí)了,公司里源代碼只要有筆者不知道的技術(shù)細(xì)節(jié),筆者就會仔細(xì)的研究清楚,筆者是不喜歡給自己留下問題的那種學(xué)習(xí)習(xí)慣。
為何要寫
筆者最近負(fù)責(zé)了消息發(fā)送的一些業(yè)務(wù)需求,由于筆者工作年限不到一年,且筆者目前只是普通本科大四學(xué)生,技術(shù)棧并不是很完善(crud程序員只是起點(diǎn)),例如國內(nèi)很多公司都在用的許雪里大神開發(fā)的xxl-job計(jì)劃任務(wù)框架,tk-mybaytis框架(筆者也不知道公司為什么不直接用mybatis而是選用了它的其他版本),自封裝的ORM框架(用法與mybatis不一樣的地方在于它需要自己寫Sql語句進(jìn)行封裝),以及各種自封裝的自研框架。由于接觸了需求,定時(shí)任務(wù)發(fā)送消息提醒用戶。所以筆者不得不學(xué)習(xí)測試一下定時(shí)任務(wù)框架來加深更多的了解,不能在開發(fā)過程中知其然不知其所以然。
定時(shí)任務(wù)的實(shí)現(xiàn)方式
說到頭,定時(shí)任務(wù)就是類似于將cron語句:?秒 分 時(shí) 日 月 周 年? 交給linux系統(tǒng)進(jìn)行執(zhí)行,到了指定的時(shí)間就得執(zhí)行這些命令,在業(yè)務(wù)需求里,我們需要系統(tǒng)去定時(shí)催促實(shí)現(xiàn)某些事情,其實(shí)這種場景非常多,要寫程序?qū)崿F(xiàn)計(jì)時(shí)的功能我覺得讀者們應(yīng)該都有自己的方式:例如 利用java線程sleep的方式,利用 timerTak的工具包,利用redis鍵值對過期消息回調(diào),利用前端計(jì)時(shí)器,利用spring框架自帶的注解@Scheduled (計(jì)劃的)搭配cron語句,利用數(shù)據(jù)庫event實(shí)現(xiàn),利用xxl-job框架實(shí)現(xiàn),利用quartz框架實(shí)現(xiàn)等等......
之前筆者就分享了一套用java線程池實(shí)現(xiàn)的定時(shí)任務(wù)工具,底層就是sleep方法,缺陷太多,不適合生產(chǎn)環(huán)境,所以為了加深對公司項(xiàng)目中xxl-job業(yè)務(wù)代碼的理解,筆者研究了quartz框架實(shí)現(xiàn)計(jì)劃任務(wù)的原理與實(shí)現(xiàn)方式,記錄下來供筆者日后進(jìn)一步深研。
下面筆者舉例部分代碼供讀者閱讀:
1、java線程sleep實(shí)現(xiàn)計(jì)時(shí)任務(wù):
?2、java工具包TimerTask實(shí)現(xiàn)定時(shí)任務(wù):
?3、利用redis鍵值對過期回調(diào):
4、前端js函數(shù)setTimeOut(函數(shù),毫秒)也可定時(shí);
5、 spring注解@Scheduled實(shí)現(xiàn)定時(shí)任務(wù):
6、利用數(shù)據(jù)庫事件event實(shí)現(xiàn):
事件調(diào)度器:?Event Scheduler可以用做定時(shí)執(zhí)行某些特定任務(wù)(例如:刪除記錄、數(shù)據(jù)統(tǒng)計(jì)報(bào)告、數(shù)據(jù)備份等等),來取代原先只能由操作系統(tǒng)的計(jì)劃任務(wù)來執(zhí)行的工作(可以精確到秒)區(qū)分表級別的觸發(fā)器Tigger(事件觸發(fā)的任務(wù)是周期時(shí)間決定,而觸發(fā)器是由表級動作決定的)。
(1)開啟數(shù)據(jù)庫事件調(diào)度:
SHOW VARIABLES LIKE 'event%';? 表中的event_scheduler值為on即為已經(jīng)開啟。
開啟:SET GLOBAL event_scheduler = 1;SET GLOBAL event_scheduler = ON;
關(guān)閉:SET GLOBAL event_scheduler = 0;SET GLOBAL event_scheduler = OFF;
如果想持久化配置項(xiàng)可以將event_scheduler=1寫入到數(shù)據(jù)庫配置文件my.cnf文件(區(qū)別my.inf文件)中。
(2)創(chuàng)建事件調(diào)度:
CREATE EVENT 【調(diào)度事件名稱】
ON SCHEDULE 【指定好調(diào)度時(shí)間】
DO 【SQL可執(zhí)行語句】
具體細(xì)節(jié)以及其他語法讀者們請自行www.baidu,com;
(3)舉例實(shí)現(xiàn)調(diào)度語句:
CREATE EVENT delete_db_and_run
ON SCHEDULE? AT? TIMESTEP '2023-04-16 16:00:00'
DO? DELETE FROM?alldata
以上就是在2023年4月16日16點(diǎn)整刪除數(shù)據(jù)表alldata的定時(shí)計(jì)劃語法
Quartz框架的技術(shù)細(xì)節(jié)與底層原理
筆者總結(jié)的技術(shù)點(diǎn)若有任何問題,歡迎讀者指出,閱即改正。
技術(shù)細(xì)節(jié):Quartz是通過數(shù)據(jù)庫持久化來實(shí)現(xiàn)計(jì)劃的數(shù)據(jù)保留,但它的原理并不是基于數(shù)據(jù)庫event的方式進(jìn)行實(shí)現(xiàn)的計(jì)劃任務(wù)。它可以擴(kuò)展更多的需求也業(yè)務(wù),你可以自定義事件只要它們繼承了Job接口。Quartz優(yōu)質(zhì)的地方在于它與xxl-job一樣是基于數(shù)據(jù)庫進(jìn)行事件調(diào)度的,這樣就算是server宕機(jī)了也不影響我們定時(shí)任務(wù)的存儲,只要在容忍時(shí)間內(nèi)重啟服務(wù)器我們的定時(shí)任務(wù)還是可以正常執(zhí)行的。
底層原理:我們只需要搞清楚兩個(gè)問題即可明白該框架的主流程脈絡(luò),至于其他的枝葉邏輯筆者并沒有深究,這兩個(gè)主流程問題就是該框架是如何將任務(wù)存入數(shù)據(jù)庫的?該框架是如何獲取定時(shí)任務(wù)的時(shí)間并判斷給出動作的?
問題1:quartz框架是如何將任務(wù)存儲進(jìn)它的配置數(shù)據(jù)表中的?
筆者還是以源碼跟蹤的方式與讀者們一起研究這里面的主要脈絡(luò):
首先在官方demo中我們能發(fā)現(xiàn)至關(guān)重要的語句,也是查找源碼的起始點(diǎn):
?org.quartz.Scheduler類實(shí)現(xiàn)的調(diào)度任務(wù)方法,就是添加計(jì)劃任務(wù)的主要實(shí)現(xiàn)代碼,筆者點(diǎn)進(jìn)源碼發(fā)現(xiàn)以下方法鏈調(diào)用:
?
?(觸發(fā)器同理也能找到類似于更新與插入方法的調(diào)用,我們先揪著一個(gè)走就行)
?這里其實(shí)已經(jīng)很清晰了,運(yùn)用了JDBC采用動態(tài)注入的方式進(jìn)行數(shù)據(jù)庫的更新與插入操作。
其實(shí)quartz還有其他數(shù)據(jù)庫類型可以使用,但是入庫的主流程脈絡(luò)大差不差,我們首先要知道它就是通過我們crud程序員最擅長的方式進(jìn)行的數(shù)據(jù)庫存儲定時(shí)任務(wù)信息的。
問題2:quartz是如何獲取定時(shí)任務(wù)的時(shí)間并判斷給出動作的?
關(guān)于這個(gè)問題,筆者查看了依賴包的層級結(jié)構(gòu),找到了core中的核心QuartzSchedulerThread類;
?顯然這就是quartz做出動作的核心線程類,關(guān)于它做了什么,我們只需要看線程的run方法即可:
由于此類的run方法行數(shù)比較長,筆者將分開敘述它做了什么:
首先在while中優(yōu)先給出了同步持有對象鎖sigLock,通先查詢halted的原子布爾型變量的判斷,如果停止的值為false則進(jìn)入等待階段等到之后進(jìn)行下一次掃描:
?下一步判斷線程池可用線程數(shù),可用數(shù)大于0后獲取將要觸發(fā)的觸發(fā)器鏈表Tiggers:
?注意方法 this.qsRsrcs.getJobStore().acquireNextTriggers 是獲取將要觸發(fā)的觸發(fā)器列表:
這里筆者直接將獲取將要觸發(fā)的觸發(fā)器列表方法核心邏輯展示出來供讀者閱讀:?
之后就是 判斷時(shí)間有沒有到該觸發(fā)的時(shí)間,因?yàn)橛|發(fā)器列表中的觸發(fā)器都是將要觸發(fā)的,判斷到快要觸發(fā)了。所以quartz這里做了等待,如果trigger的nextFireTime比當(dāng)前時(shí)間大2ms則循環(huán)等待。timeUntilTrigger就是nextFireTime和當(dāng)前時(shí)間之間的差值,它在循環(huán)中不斷更新,直到它的值非常小了,之后才繼續(xù)向下到真正的觸發(fā)代碼:
?然后就是觸發(fā)了:
?我們繼續(xù)看觸發(fā)方法tiggersFired()的部分核心代碼:
?之后的邏輯方法就是數(shù)據(jù)庫操作,記錄操作:
?將庫修改之后就要進(jìn)行腳本的執(zhí)行了(這里需要注意的是執(zhí)行是線程的異步操作,并行執(zhí)行,但也有一定的控制邏輯),根據(jù)數(shù)據(jù)庫的觸發(fā)結(jié)果獲取腳本shell:
?拿到shell之后就可以到線程池中丟過去執(zhí)行了:
?實(shí)現(xiàn)了Runnable接口的shell當(dāng)然可以直接執(zhí)行了,最后我們發(fā)現(xiàn)它對于未獲取到將要觸發(fā)的觸發(fā)器列表時(shí),它會自動等待:
?開始獲取鎖等待是為了定期執(zhí)行,后面的獲取鎖等待是為了在等最近可以觸發(fā)的觸發(fā)器列表。
到這里筆者和讀者們大概也明白了quartz的獲取判斷與觸發(fā)邏輯,理解了第二大主流程脈絡(luò),當(dāng)然里面的邏輯肯定不止這些,但是筆者認(rèn)為理解了主流程脈絡(luò)結(jié)合實(shí)踐加深吃入程度即可。
?Quartz框架的實(shí)現(xiàn)demo實(shí)踐
筆者也做了一個(gè)小demo實(shí)現(xiàn)我們的計(jì)劃任務(wù)業(yè)務(wù)需求,當(dāng)然僅僅供讀者們閱讀與學(xué)習(xí),不支持生產(chǎn)使用。
依賴:
<!-- quartz依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.5.4</version>
</dependency>
quartz.sql
#1 保存已經(jīng)觸發(fā)的觸發(fā)器狀態(tài)信息
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
#2 存放暫停掉的觸發(fā)器表表
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
#3 調(diào)度器狀態(tài)表
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
#4 存儲程序的悲觀鎖的信息(假如使用了悲觀鎖)
DROP TABLE IF EXISTS QRTZ_LOCKS;
#5 簡單的觸發(fā)器表
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
#6 存儲兩種類型的觸發(fā)器表
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
#7 定時(shí)觸發(fā)器表
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
#8 以blob 類型存儲的觸發(fā)器
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
#9 觸發(fā)器表
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
#10 job 詳細(xì)信息表
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
#11 日歷信息表
DROP TABLE IF EXISTS QRTZ_CALENDARS;
#job 詳細(xì)信息表
CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512) NULL,
STR_PROP_2 VARCHAR(512) NULL,
STR_PROP_3 VARCHAR(512) NULL,
INT_PROP_1 INT NULL,
INT_PROP_2 INT NULL,
LONG_PROP_1 BIGINT NULL,
LONG_PROP_2 BIGINT NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR(1) NULL,
BOOL_PROP_2 VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
配置文件application.yml
server:
port: 9000
spring:
application:
name: quartz-service
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/your quartz db ?useUnicode=true&characterEncoding=utf8
username: your db username
password: your db password
driver-class-name: com.mysql.cj.jdbc.Driver
quartz:
# 相關(guān)屬性配置
properties:
org:
quartz:
# 數(shù)據(jù)源
dataSource:
globalJobDataSource:
# URL必須大寫
URL: jdbc:mysql://127.0.0.1:3306/your quartz db ?useUnicode=true&characterEncoding=utf-8&useSSL=false
driver: com.mysql.cj.jdbc.Driver
maxConnections: 5
username: your db username
password: your db password
# 必須指定數(shù)據(jù)源類型
provider: hikaricp
scheduler:
instanceName: globalScheduler
# 實(shí)例id
#instanceId: AUTO
type: com.alibaba.druid.pool.DruidDataSource
jobStore:
# 數(shù)據(jù)源
dataSource: globalJobDataSource
# JobStoreTX將用于獨(dú)立環(huán)境,提交和回滾都將由這個(gè)類處理
class: org.quartz.impl.jdbcjobstore.JobStoreTX
# 驅(qū)動配置
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前綴
tablePrefix: QRTZ_
# 線程池配置
threadPool:
class: org.quartz.simpl.SimpleThreadPool
# 線程數(shù)
threadCount: 10
# 優(yōu)先級
threadPriority: 5
實(shí)體類
@Data
public class JobInfo {
private String jobName;
private String jobGroup;
private String triggerName;
private String triggerGroup;
private String cron;
private String className;
private String status;
private String nextTime;
private String prevTime;
private String config;
}
@Data
public class Message {
private String title;
private String message;
private String jobGroup;
private String jobName;
private String sendUserName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String sendDate;
}
?核心任務(wù)處理類
@Component
public class JobHandler {
@Resource
private Scheduler scheduler;
public void addJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
Assert.notNull(jobInfo, LocalDateTime.now().toString() + "-> 任務(wù)信息為null拒絕生成定時(shí)任務(wù)");
JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
if (!scheduler.checkExists(jobKey)) {
Class<Job> jobClass = (Class<Job>) Class.forName(jobInfo.getClassName());
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(jobKey)
.withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
.withIdentity(jobInfo.getJobName())
.build();
jobDetail.getJobDataMap().put("config", jobInfo.getConfig());
TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCron()))
.build();
scheduler.scheduleJob(jobDetail,trigger);
} else {
throw new SchedulerException(jobInfo.getJobName() + "->定時(shí)任務(wù)計(jì)劃庫已存在");
}
}
public void pauseJob(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.pauseJob(jobKey);
}
}
public void resumeJob(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.resumeJob(jobKey);
}
}
public Boolean deleteJob(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
return scheduler.deleteJob(jobKey);
}
return false;
}
public JobInfo getJobInfo(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName,jobGroup);
if (!scheduler.checkExists(jobKey)){
return null;
}
List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
Assert.notNull(triggersOfJob, LocalDateTime.now().toString()+"->觸發(fā)器信息為空->"+jobGroup+"/"+jobName);
TriggerKey key = triggersOfJob.get(0).getKey();
Trigger.TriggerState state = scheduler.getTriggerState(key);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
JobInfo jobInfo = new JobInfo();
jobInfo.setJobGroup(jobGroup);
jobInfo.setJobName(jobName);
jobInfo.setTriggerGroup(key.getGroup());
jobInfo.setJobName(key.getName());
jobInfo.setClassName(jobDetail.getJobClass().getName());
jobInfo.setStatus(state.toString());
if (Objects.nonNull(jobDetail.getJobDataMap())){
jobInfo.setConfig(JSONObject.toJSONString(jobDetail.getJobDataMap()));
}
CronTrigger trigger = (CronTrigger) triggersOfJob.get(0);
jobInfo.setCron(trigger.getCronExpression());
return jobInfo;
}
}
事件類型
@Component
@DisallowConcurrentExecution
public class PlanRemindJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("執(zhí)行計(jì)劃任務(wù)" + jobExecutionContext.getJobDetail().getDescription());
}
}
@Component
@DisallowConcurrentExecution
public class TimeEventJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("執(zhí)行定時(shí)任務(wù)" + jobExecutionContext.getJobDetail().getDescription());
}
}
事件類型枚舉
public enum JobType {
//計(jì)劃任務(wù)與定時(shí)任務(wù)
PLAN_REMIND_MESSAGE ,
TIME_EVENT_MESSAGE
}
業(yè)務(wù)接口
public interface QuartzService {
Boolean insertMessage(Message message, JobType jobType) throws SchedulerException, ClassNotFoundException;
JobInfo getJob(Message message);
Boolean deleteJob(Message message);
Boolean resumeJob(Message message);
}
?業(yè)務(wù)實(shí)現(xiàn)類
@Service("QuartzService")
public class QuartzServiceImpl implements QuartzService {
@Resource
private JobHandler jobHandler;
@Override
public synchronized Boolean insertMessage(Message message, JobType jobType) {
JobInfo jobInfo = JobGenerator.getJobInfo(message, jobType);
try {
jobHandler.addJob(jobInfo);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public JobInfo getJob(Message message) {
try {
return jobHandler.getJobInfo(message.getJobGroup(), message.getJobName());
} catch (SchedulerException e) {
e.printStackTrace();
return null;
}
}
@Override
public synchronized Boolean deleteJob(Message message) {
try {
return jobHandler.deleteJob(message.getJobGroup(), message.getJobName());
} catch (SchedulerException e) {
e.printStackTrace();
return false;
}
}
@Override
public synchronized Boolean resumeJob(Message message) {
try {
jobHandler.resumeJob(message.getJobGroup(), message.getJobName());
return true;
} catch (SchedulerException e) {
e.printStackTrace();
return false;
}
}
}
工具類
cron與日期(“yyyy-MM-dd hh:mm:ss”)轉(zhuǎn)換類
public class CronGenerator {
public static String geCron(Message message){
String date = message.getSendDate().toString();
String yyyy = date.substring(0, 4);
String MM = date.substring(5, 7);
String dd = date.substring(8, 10);
String hh = date.substring(11, 13);
String mm = date.substring(14, 16);
String ss = date.substring(17, 19);
return ss+" "+mm+" "+hh+" "+dd+" "+MM+" "+"? "+yyyy;
}
}
JobInfo實(shí)體轉(zhuǎn)換工具類
public class JobGenerator {
public static JobInfo getJobInfo(Message message, JobType jobType) {
String cron = CronGenerator.geCron(message);
JobInfo jobInfo = new JobInfo();
jobInfo.setCron(cron);
if (jobType == JobType.PLAN_REMIND_MESSAGE){
jobInfo.setClassName("com.hlc.quartzservice.jobType.PlanRemindJob");
jobInfo.setJobGroup(message.getJobGroup());
jobInfo.setJobName(message.getJobName());
jobInfo.setTriggerGroup("PLAN_REMIND_MESSAGE");
jobInfo.setTriggerName("計(jì)劃任務(wù)觸發(fā)器");
jobInfo.setConfig(message.getMessage());
}else if (jobType == JobType.TIME_EVENT_MESSAGE){
jobInfo.setClassName("com.hlc.quartzservice.jobType.TimeEventJob");
jobInfo.setJobGroup(message.getJobGroup());
jobInfo.setJobName(message.getJobName());
jobInfo.setTriggerGroup("TIME_EVENT_MESSAGE");
jobInfo.setTriggerName("定時(shí)任務(wù)觸發(fā)器");
jobInfo.setConfig(message.getMessage());
}
return jobInfo;
}
}
開啟應(yīng)用程序進(jìn)行測試
@SpringBootApplication
public class QuartzServiceApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzServiceApplication.class, args);
QuartzService quartzService = SpringContextHolder.getBean(QuartzService.class);
Message message = new Message();
message.setMessage("測試");
message.setSendDate("2023-04-15 14:30:00");
message.setTitle("消息");
message.setSendUserName("hlc");
try {
quartzService.insertMessage(message, JobType.PLAN_REMIND_MESSAGE);
} catch (SchedulerException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
靜態(tài)方法獲取bean的獲取容器上下文工具類(實(shí)現(xiàn)ApplicationContextAware接口)
@Component
@Lazy(value = false)
public class SpringContextHolder implements ApplicationContextAware {
/**
* 將上下文靜態(tài)設(shè)置,在初始化組件時(shí)就進(jìn)行靜態(tài)上下文的覆蓋(這個(gè)覆蓋是將遠(yuǎn)spring容器的上下文對象引用加到我們預(yù)定設(shè)置)
*/
private static ApplicationContext applicationContext = null;
public static ApplicationContext getApplicationContext() {
assertContextInjected();
return applicationContext;
}
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> beanType) {
assertContextInjected();
return applicationContext.getBean(beanType);
}
@Override
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
SpringContextHolder.applicationContext = applicationContext;
}
public void destroy() {
applicationContext = null;
}
private static void assertContextInjected() {
Assert.notNull(applicationContext,
"applicationContext屬性未注入, 請?jiān)赼pplicationContext.xml中定義SpringContextHolder.");
}
public static void pushEvent(ApplicationEvent event){
assertContextInjected();
applicationContext.publishEvent(event);
}
}
還有另外一種方法可以測試,就是查庫,看看數(shù)據(jù)庫表中有沒有發(fā)生數(shù)據(jù)變化?文章來源:http://www.zghlxwxcb.cn/news/detail-422125.html
給的么詳細(xì),確定不點(diǎn)點(diǎn)贊,收藏一下嗎?文章來源地址http://www.zghlxwxcb.cn/news/detail-422125.html
到了這里,關(guān)于你知道怎么實(shí)現(xiàn)定時(shí)任務(wù)嗎?的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!