SpringBoot集成Quartz實現(xiàn)持久化定時接口調用任務
一、基本概念
Quartz 是功能強大的開源作業(yè)調度庫,幾乎可以集成到任何 Java 應用程序中,從最小的獨立應用程序到最大的電子商務系統(tǒng)。Quartz 可用于創(chuàng)建簡單或復雜的計劃,以執(zhí)行數(shù)以萬計的工作;可以執(zhí)行您編寫的所有內(nèi)容。
Spring Boot 官方也對 Quartz 調度器進行了集成,Spring boot 官網(wǎng)文檔:Quartz Scheduler,Java JDK 也帶有 計時器 Timer 以及 定時執(zhí)行服務 ScheduledExecutorService ,Spring 也提供了 @Scheduled 執(zhí)行定時任務。
如果實際環(huán)境中定時任務過多,處理頻繁,建議適應第三方封裝的調度框架,因為定時器操作底層都是多線程的操作,任務的啟動、暫停、恢復、刪除、實質是線程的啟動、暫停、中斷、喚醒等操作。
二、Quartz-scheduler 的核心流程
Scheduler - 調度器
1、Scheduler 用來對 Trigger 和 Job 進行管理,Trigger 和 JobDetail 可以注冊到 Scheduler 中,兩者在 Scheduler 中都擁有自己的唯一的組(group)和名稱(name)用來進行彼此的區(qū)分,Scheduler 可以通過任務組和名稱來對 Trigger 和 JobDetail 進行管理。
2、每個 Scheduler 都有一個 SchedulerContext,用來保存 Scheduler 的上下文數(shù)據(jù),Job 和 Trigger 都可以獲取其中的信息。
3、Scheduler 是由 SchedulerFactory 創(chuàng)建,它有兩個實現(xiàn):DirectSchedulerFactory 、StdSchdulerFactory ,前者可以用來在代碼里定制 Schduler 參數(shù),后者直接讀取 classpath 下的 quartz.properties(不存在就都使用默認值)配置來實例化 Scheduler。
Job - 任務
1、Job 是一個任務接口,開發(fā)者可以實現(xiàn)該接口定義自己的任務,JobExecutionContext 中提供了調度上下文的各種信息。
2、Job 中的任務有可能并發(fā)執(zhí)行,例如任務的執(zhí)行時間過長,而每次觸發(fā)的時間間隔太短,則會導致任務會被并發(fā)執(zhí)行。如果是并發(fā)執(zhí)行,就需要一個數(shù)據(jù)庫鎖去避免一個數(shù)據(jù)被多次處理??梢栽?execute()方法上添加 @DisallowConcurrentExecution 注解解決這個問題。
JobDetail - 任務詳情
1、JobDetail 對象是在將 job 注冊到 scheduler 時,由客戶端程序創(chuàng)建的,它包含 job 的各種屬性設置,以及用于存儲 job 實例狀態(tài)信息的 JobDataMap。
2、JobDetail 由 JobBuilder 創(chuàng)建/定義,Quartz 不存儲 Job 的實際實例,但是允許通過使用 JobDetail 定義一個實例。
3、Job 有一個與其關聯(lián)的名稱和組,應該在單個 Scheduler 中唯一標識它們。
4、一個 Trigger(觸發(fā)器) 只能對應一個 Job(任務),但是一個 Job 可以對應多個 Trigger。JobDetal 與 Trigger 一對多
Trigger - 觸發(fā)器
1、Trigger 用于觸發(fā) Job 的執(zhí)行。TriggerBuilder 用于定義/構建觸發(fā)器實例。
2、Trigger也有一個相關聯(lián)的 JobDataMap,用于給Job傳遞一些觸發(fā)相關的參數(shù)。
3、Quartz自帶了各種不同類型的 Trigger,最常用的主要是SimpleTrigger和CronTrigger。
JobDataMap
1、JobDataMap 實現(xiàn)了 JDK 的 Map 接口,可以以 Key-Value 的形式存儲數(shù)據(jù)。
2、JobDetail、Trigger 實現(xiàn)類中都定義 JobDataMap 成員變量及其 getter、setter 方法,可以用來設置參數(shù)信息,Job 執(zhí)行 execute() 方法的時候,JobExecutionContext 可以獲取到 JobDataMap 中的信息。
三、實踐
1、新建一個quartz-service服務
添加依賴:
<modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>quartz-servicer</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>11</maven.compiler.source> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.5</version> </dependency> <!-- 公共依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.19</version> </dependency> <!-- quartz依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.31</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.14</version> <exclusions> <exclusion> <artifactId>spring-boot-autoconfigure</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
2、配置數(shù)據(jù)源相關鏈接
quartz需要單據(jù)的數(shù)據(jù)庫,所以需要單據(jù)創(chuàng)建一個庫來給quartz使用,我新建了一個scheduler的庫
#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 調度器狀態(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 定時觸發(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 詳細信息表 DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; #11 日歷信息表 DROP TABLE IF EXISTS QRTZ_CALENDARS; #job 詳細信息表 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) ); #觸發(fā)器表 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) ); #簡單的觸發(fā)器表,包括重復次數(shù),間隔,以及已觸發(fā)的次數(shù) 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) ); #定時觸發(fā)器表,存儲 cron trigger,包括 cron 表達式和時區(qū)信息 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) ); #存儲calendarintervaltrigger和dailytimeintervaltrigger兩種類型的觸發(fā)器 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) ); #以blob 類型存儲的觸發(fā)器 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) ); #日歷信息表, quartz可配置一個日歷來指定一個時間范圍 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) ); #存放暫停掉的觸發(fā)器表表 CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); # 存儲與已觸發(fā)的 trigger 相關的狀態(tài)信息,以及相聯(lián) job 的執(zhí)行信息 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) ); 3、 調度器狀態(tài)表 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) ); 4、 存儲程序的悲觀鎖的信息(假如使用了悲觀鎖) CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) );
3、創(chuàng)建配置文件
Quartz 使用一個名為 quartz.properties 的屬性文件進行信息配置,必須位于 classpath 下,是 StdSchedulerFactory 用于創(chuàng)建 Scheduler 的默認屬性文件。默認情況下 StdSchedulerFactory 從類路徑下加載名為 “quartz.properties” 的屬性文件,如果失敗,則加載 org/quartz 包中的“quartz.properties”文件,因為我需要的是新建一個Scheduler服務,所以直接使用application.yml,配置如下:
datasource: url: jdbc:mysql://127.0.0.1:3306/scheduler username: *** password: **** driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #定時配置 quartz: #相關屬性配置 properties: org: quartz: scheduler: instanceName: local-scheduler-svc instanceId: AUTO jobStore: #表示 quartz 中的所有數(shù)據(jù),比如作業(yè)和觸發(fā)器的信息都保存在內(nèi)存中(而不是數(shù)據(jù)庫中) class: org.springframework.scheduling.quartz.LocalDataSourceJobStore # 驅動配置 driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 表前綴 tablePrefix: QRTZ_ #是否為集群 isClustered: false clusterCheckinInterval: 10000 useProperties: false dataSource: quartzDs #線程池配置 threadPool: class: org.quartz.simpl.SimpleThreadPool #線程數(shù) threadCount: 10 #優(yōu)先級 threadPriority: 5 #線程繼承上下文類加載器的初始化線程 threadsInheritContextClassLoaderOfInitializingThread: true #數(shù)據(jù)庫方式 job-store-type: JDBC #初始化表結構 jdbc: initialize-schema: NEVER
4、新建一個任務實體類JobInfo,用戶新建,傳遞任務信息
jobName:任務名稱
jobGroup:任務組
jsonParams:任務執(zhí)行信息(在用戶定時遠程調用接口的時候,我們可以接口信息封裝到這個Map中)
cron:定時任務的cron表達式
timeZoneId:定制執(zhí)行任務的時區(qū)
triggerTime:定時器時間(目前沒用上)
@Data public class JobInfo { private String jobName; private String jobGroup; private Map<String, Object> jsonParams; private String cron; private String timeZoneId; private Date triggerTime; }
5、新建一個任務執(zhí)行類HttpRemoteJob 實現(xiàn) Job接口,重寫execute()方法
execute()里面就是任務的邏輯:
① 使用HttpURLConnection
發(fā)送網(wǎng)絡請求,利用BufferedReader接收請求返回的結果
② 在任務的Description中取出定時請求的接口信息,解析Description,獲取請求url
③ 編輯請求信息,通過URL類編輯請求信息,使用HttpURLConnection發(fā)送請求,并接收請求返回的狀態(tài)碼,根據(jù)狀態(tài)碼,判斷是否請求成功,請求成功便通過BufferedReader讀取響應信息,返回請求結果
import com.alibaba.fastjson.JSONObject; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Objects; @DisallowConcurrentExecution public class HttpRemoteJob implements Job { //日志 private static final Logger log = LoggerFactory.getLogger(HttpRemoteJob.class); @Override public void execute(JobExecutionContext context)throws JobExecutionException { //用于發(fā)送網(wǎng)絡請求 HttpURLConnection connection = null; //用于接收請求返回的結果 BufferedReader bufferedReader = null; //獲取任務Description述,之前我們把接口請求的信息放在Description里面了 String jsonParams = context.getJobDetail().getDescription(); if (StringUtils.isEmpty(jsonParams)){ return; } //解析Description,獲取請求url JSONObject jsonObj= (JSONObject) JSONObject.parse(jsonParams); String callUrl = jsonObj.getString("callUrl"); if(StringUtils.isEmpty(callUrl)) { return; } try { //編輯請求信息 URL realUrl = new URL(callUrl); connection = (HttpURLConnection) realUrl.openConnection(); connection.setRequestMethod("GET"); connection.setDoOutput(true); connection.setDoInput(true); connection.setUseCaches(false); connection.setReadTimeout(5 * 1000); connection.setConnectTimeout(3 * 1000); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); //發(fā)送請求 connection.connect(); //獲取請求返回的狀態(tài)嗎 int statusCode = connection.getResponseCode(); if (statusCode != 200){ //請求失敗拋出異常 throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid."); } //如果返回值正常,數(shù)據(jù)在網(wǎng)絡中是以流的形式得到服務端返回的數(shù)據(jù) bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; // 從流中讀取響應信息 while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line); } String responseMsg = stringBuilder.toString(); log.info(responseMsg); } catch (Exception e) { log.error(e.getMessage()); } finally { //關閉流與請求連接 try { if (Objects.nonNull(bufferedReader)){ bufferedReader.close(); } if (Objects.nonNull(connection)){ connection.disconnect(); } } catch (Exception e) { log.error(e.getMessage()); } } } }
@DisallowConcurrentExecution
的作用:
Quartz定時任務默認是并發(fā)執(zhí)行的,不會等待上一次任務執(zhí)行完畢,只要有間隔時間到就會執(zhí)行, 如果定時任執(zhí)行太長,會長時間占用資源,導致其它任務堵塞。
@DisallowConcurrentExecution
這個注解是加在Job類上的,是禁止并發(fā)執(zhí)行多個相同定義的JobDetail, , 但并不是不能同時執(zhí)行多個Job, 而是不能并發(fā)執(zhí)行同一個Job Definition(由JobDetail定義), 但是可以同時執(zhí)行多個不同的JobDetail。
JobExecutionContext
類可以獲取很多任務的信息:
@Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobKey jobKey = jobExecutionContext.getJobDetail().getKey(); //工作任務名稱 String jobName = jobKey.getName(); //工作任務組名稱 String groupName = jobKey.getGroup(); //任務類名稱(帶路徑) String classPathName = jobExecutionContext.getJobDetail().getJobClass().getName(); //任務類名稱 String className = jobExecutionContext.getJobDetail().getJobClass().getSimpleName(); //獲取Trigger內(nèi)容 TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey(); //觸發(fā)器名稱 String triggerName = triggerKey.getName(); //出發(fā)組名稱(帶路徑) String triggerPathName = jobExecutionContext.getTrigger().getClass().getName(); //觸發(fā)器類名稱 String triggerClassName = jobExecutionContext.getTrigger().getClass().getSimpleName(); }
注意:
//解析Description,獲取請求url JSONObject jsonObj= (JSONObject) JSONObject.parse(jsonParams); String callUrl = jsonObj.getString("callUrl");
之前我們封裝JobInfo信息是將Map<String, Object> jsonParams保存接口請求信息,們?nèi)ソ馕鼋涌谡埱蟮臅r候也要用callUrl
去取,那么在封裝JobInfo類時,Map中就需要指定key是callUrl
,不然取不到就會報錯了。
6、創(chuàng)建定時任務業(yè)務層,創(chuàng)建 JobService接口和 JobServiceImpl實現(xiàn)類
JobServiceImpl業(yè)務邏輯:
JobInfo攜帶這我們需要新建任務的信息
① 通過jobName和jobGroup可以查詢到任務唯一的jobKey,通過getJobDetail(jobKey),判斷是否已有這個任務,有的話先刪除在新增這個任務
② 新建一個任務JobDetail,withDescription屬性中是指任務描述,JobInfo類中JsonParams屬性是一個Map,這里需要將Map格式化一下,不然無法賦給withDescription,這個JsonUtils
在下面:
//任務詳情 JobDetail jobDetail = JobBuilder.newJob(HttpRemoteJob.class) .withDescription(JsonUtils.object2Json(jobInfo.getJsonParams())) //任務描述 .withIdentity(jobKey) //指定任務 .build();
③ 創(chuàng)建觸發(fā)器,觸發(fā)器有多種類型,需要定時執(zhí)行就使用cron表達式,創(chuàng)建cron調度器建造器CronScheduleBuilder
,再創(chuàng)建Trigger觸發(fā)器,調度器建造器有很多種,除了我下面用到的簡單調度器構造器SimpleTrigger
,還有:
CalendarIntervalScheduleBuilder
: 每隔一段時間執(zhí)行一次(年月日)
DailyTimeIntervalScheduleBuilder
: 設置年月日中的某些固定日期,可以設置執(zhí)行總次數(shù)
以后我們再單獨寫一片介紹;
CronScheduleBuilder和SimpleTrigger的區(qū)別在于:CronScheduleBuilder是通過cron表達式定時某個時間點或多個時間點定時直接,而SimpleTrigger是周期性執(zhí)行,著重與時間間隔,著重與周期執(zhí)行;
④ 將任務添加到Scheduler中
此外還有一些Scheduler的其他方法:
獲取任務觸發(fā)器 :TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
停止觸發(fā)器 :scheduler.pauseTrigger(triggerKey);
移除觸發(fā)器:scheduler.unscheduleJob(triggerKey);
刪除任務:scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup));
根據(jù)jobName,jobGroup獲取jobKey 恢復任務:scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup));
根據(jù)jobName,jobGroup獲取jobKey 暫停任務: scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup));
根據(jù)jobName,jobGroup獲取jobKey 立即執(zhí)行任務: scheduler.triggerJob(JobKey.jobKey(jobName,jobGroup));
在下面的實現(xiàn)類代碼中有很好的用例;
JsonUtils工具類
public class JsonUtils { public static final ObjectMapper OBJECT_MAPPER = createObjectMapper(); private static final ObjectMapper IGNORE_OBJECT_MAPPER = createIgnoreObjectMapper(); private static ObjectMapper createIgnoreObjectMapper() { ObjectMapper objectMapper = createObjectMapper(); objectMapper.addMixIn(Object.class, DynamicMixIn.class); return objectMapper; } public static ObjectMapper createObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); objectMapper.registerModule(new JavaTimeModule()); return objectMapper; } public static String object2Json(Object o) { StringWriter sw = new StringWriter(); JsonGenerator gen = null; try { gen = new JsonFactory().createGenerator(sw); OBJECT_MAPPER.writeValue(gen, o); } catch (IOException e) { throw new RuntimeException("Cannot serialize object as JSON", e); } finally { if (null != gen) { try { gen.close(); } catch (IOException e) { throw new RuntimeException("Cannot serialize object as JSON", e); } } } return sw.toString(); } }
JobService接口
import liu.qingxu.domain.JobInfo; import org.springframework.web.bind.annotation.RequestBody; /** * @module * @author: qingxu.liu * @date: 2022-11-15 14:37 * @copyright **/ public interface JobService { /** * 新建一個定時任務 * @param jobInfo 任務信息 * @return 任務信息 */ JobInfo save(@RequestBody JobInfo jobInfo); /** * 新建一個簡單定時任務 * @param jobInfo 任務信息 * @return 任務信息 */ JobInfo simpleSave(@RequestBody JobInfo jobInfo); /** * 刪除任務 * @param jobName 任務名稱 * @param jobGroup 任務組 */ void remove( String jobName,String jobGroup); /** * 恢復任務 * @param jobName 任務名稱 * @param jobGroup 任務組 */ void resume(String jobName, String jobGroup); /** * 暫停任務 * @param jobName 任務名稱 * @param jobGroup 任務組 */ void pause(String jobName, String jobGroup); /** * 立即執(zhí)行任務一主要是用于執(zhí)行一次任務的場景 * @param jobName 任務名稱 * @param jobGroup 任務組 */ void trigger(String jobName, String jobGroup); }
JobServiceImpl實現(xiàn)類
import liu.qingxu.domain.JobInfo; import liu.qingxu.executors.HttpRemoteJob; import liu.qingxu.service.JobService; import liu.qingxu.utils.JsonUtils; import org.quartz.CronScheduleBuilder; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Objects; import java.util.TimeZone; /** * @module * @author: qingxu.liu * @date: 2022-11-15 14:48 * @copyright **/ @Service public class JobServiceImpl implements JobService { @Autowired private Scheduler scheduler; @Override public JobInfo save(JobInfo jobInfo) { //查詢是否已有相同任務 jobKey可以唯一確定一個任務 JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup()); try { JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (Objects.nonNull(jobDetail)){ scheduler.deleteJob(jobKey); } } catch (SchedulerException e) { e.printStackTrace(); } //任務詳情 JobDetail jobDetail = JobBuilder.newJob(HttpRemoteJob.class) .withDescription(JsonUtils.object2Json(jobInfo.getJsonParams())) //任務描述 .withIdentity(jobKey) //指定任務 .build(); //根據(jù)cron,TimeZone時區(qū),指定執(zhí)行計劃 CronScheduleBuilder builder = CronScheduleBuilder.cronSchedule(jobInfo.getCron()) .inTimeZone(TimeZone.getTimeZone(jobInfo.getTimeZoneId())); //觸發(fā)器 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).startNow() .withSchedule(builder) .build(); //添加任務 try { scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { System.out.println(e.getMessage()); } return jobInfo; } @Override public JobInfo simpleSave(JobInfo jobInfo) { JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup()); //作業(yè)名稱及其組名 //判斷是否有相同的作業(yè) try { JobDetail jobDetail = scheduler.getJobDetail(jobKey); if(jobDetail != null){ scheduler.deleteJob(jobKey); } } catch (SchedulerException e) { e.printStackTrace(); } //定義作業(yè)的詳細信息,并設置要執(zhí)行的作業(yè)類名,設置作業(yè)名稱及其組名 JobDetail jobDetail = JobBuilder.newJob(HttpRemoteJob.class) .withDescription(JsonUtils.object2Json(jobInfo.getJsonParams())) .withIdentity(jobKey) .build() ; //簡單觸發(fā)器,著重與時間間隔 SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()) .startAt(jobInfo.getTriggerTime()) .build(); try { scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { System.out.println(e.getMessage()); } return jobInfo; } @Override public void remove(String jobName, String jobGroup) { //獲取任務觸發(fā)器 TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); try { //停止觸發(fā)器 scheduler.pauseTrigger(triggerKey); //移除觸發(fā)器 scheduler.unscheduleJob(triggerKey); //刪除任務 scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup)); } catch (SchedulerException e) { System.out.println(e.getMessage()); } } @Override public void resume(String jobName, String jobGroup) { try { //根據(jù)jobName,jobGroup獲取jobKey 恢復任務 scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup)); } catch (SchedulerException e) { System.out.println(e.getMessage()); } } @Override public void pause(String jobName, String jobGroup) { try { //根據(jù)jobName,jobGroup獲取jobKey 暫停任務 scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup)); } catch (SchedulerException e) { System.out.println(e.getMessage()); } } @Override public void trigger(String jobName, String jobGroup) { try { //根據(jù)jobName,jobGroup獲取jobKey 立即執(zhí)行任務 scheduler.triggerJob(JobKey.jobKey(jobName,jobGroup)); } catch (SchedulerException e) { System.out.println(e.getMessage()); } } }
到此為止,我的定時調用接口任務已經(jīng)完成了,現(xiàn)在我們寫個Controller來試著調用一下:
我們調用test接口,新建一個任務,讓任務每隔5秒調用runTest接口,然后在調用deleteTest接口刪除任務;
@RestController @RequestMapping("/quartz/job") public class QuartzController { private final JobService jobService; public QuartzController(JobService jobService) { this.jobService = jobService; } @GetMapping("/test") public void test(){ JobInfo jobInfo = new JobInfo(); jobInfo.setJobName("test-job"); jobInfo.setJobGroup("test"); jobInfo.setTimeZoneId("Asia/Shanghai"); //時區(qū)指定上海 jobInfo.setCron("0/5 * * * * ? "); //每5秒執(zhí)行一次 Map<String, Object> params = new HashMap<>(); //添加需要調用的接口信息 String callUrl = "http://127.0.0.1:8080/quartz/job/test/run"; params.put("callUrl", callUrl); jobInfo.setJsonParams(params); jobService.save(jobInfo); } @GetMapping("/test/run") public void runTest(){ System.out.println(new Date()); } @GetMapping("/test/delete") public void deleteTest(){ jobService.remove("test-job","test"); System.out.println("任務已刪除"); } }
四、驗證結果
可以看到runTest接口每隔5秒就被任務調用了一個,這時候我們?nèi)タ磾?shù)據(jù)庫中的scheduler庫的QRTZ_JOB_DETAILS表和QRTZ_TRIGGERS表,可以看到我們我們定時任務的信息,表示我們的定時任務持久化成功,這個時候你關掉服務器,再重啟任務也會定時去執(zhí)行runTest接口;
然后我們再調用deleteTest接口刪除任務
此時再看去看數(shù)據(jù)庫中的scheduler庫的QRTZ_JOB_DETAILS表和QRTZ_TRIGGERS表中就沒有之前的任務數(shù)據(jù)了
以上就是SpringBoot集成Quartz實現(xiàn)持久化定時接口調用任務的詳細內(nèi)容,更多關于SpringBoot Quartz實現(xiàn)定時任務的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot3.x打包Docker容器的實現(xiàn)
這篇文章主要介紹了SpringBoot3.x打包Docker容器的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11SpringBoot使用Flyway進行數(shù)據(jù)庫遷移的實現(xiàn)示例
Flyway是一個數(shù)據(jù)庫遷移工具,它提供遷移歷史和回滾的功能,本文主要介紹了如何使用Flyway來管理Spring Boot應用程序中的SQL數(shù)據(jù)庫架構,感興趣的可以了解一下2023-08-08Java數(shù)組創(chuàng)建的3種方法6種寫法代碼示例
這篇文章主要給大家介紹了關于Java數(shù)組創(chuàng)建的3種方法6種寫法,在Java中我們可以使用關鍵字new來創(chuàng)建一個數(shù)組,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-01-01Spring擴展之基于HandlerMapping實現(xiàn)接口灰度發(fā)布實例
這篇文章主要介紹了Spring擴展之基于HandlerMapping實現(xiàn)接口灰度發(fā)布實例,灰度發(fā)布是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式,灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時候就可以發(fā)現(xiàn)、調整問題,以保證其影響度,需要的朋友可以參考下2023-08-08