龙空技术网

SpringBoot:如何集成Quartz实现持久化定时接口调用任务?

晾干的红领巾 1816

前言:

此刻各位老铁们对“quartzcom”大约比较重视,小伙伴们都想要分析一些“quartzcom”的相关资讯。那么小编在网摘上搜集了一些关于“quartzcom””的相关内容,希望小伙伴们能喜欢,同学们快快来了解一下吧!

一、基本概念

Quartz 是功能强大的开源作业调度库,几乎可以集成到任何 Java 应用程序中,从最小的独立应用程序到最大的电子商务系统。Quartz 可用于创建简单或复杂的计划,以执行数以万计的工作;可以执行您编写的所有内容。

Spring Boot 官方也对 Quartz 调度器进行了集成,Spring boot 官网文档:Quartz Scheduler,Java JDK 也带有 计时器 Timer 以及 定时执行服务 ScheduledExecutorService ,Spring 也提供了 @Scheduled 执行定时任务。

如果实际环境中定时任务过多,处理频繁,建议适应第三方封装的调度框架,因为定时器操作底层都是多线程的操作,任务的启动、暂停、恢复、删除、实质是线程的启动、暂停、中断、唤醒等操作。

二、Quartz-scheduler 的核心流程

Scheduler - 调度器

1、Scheduler 用来对 Trigger 和 Job 进行管理,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中都拥有自己的唯一的组(group)和名称(name)用来进行彼此的区分,Scheduler 可以通过任务组和名称来对 Trigger 和 JobDetail 进行管理。

2、每个 Scheduler 都有一个 SchedulerContext,用来保存 Scheduler 的上下文数据,Job 和 Trigger 都可以获取其中的信息。

3、Scheduler 是由 SchedulerFactory 创建,它有两个实现:DirectSchedulerFactory 、StdSchdulerFactory ,前者可以用来在代码里定制 Schduler 参数,后者直接读取 classpath 下的 quartz.properties(不存在就都使用默认值)配置来实例化 Scheduler。

Job - 任务

1、Job 是一个任务接口,开发者可以实现该接口定义自己的任务,JobExecutionContext 中提供了调度上下文的各种信息。

2、Job 中的任务有可能并发执行,例如任务的执行时间过长,而每次触发的时间间隔太短,则会导致任务会被并发执行。如果是并发执行,就需要一个数据库锁去避免一个数据被多次处理。可以在 execute()方法上添加 @DisallowConcurrentExecution 注解解决这个问题。

JobDetail - 任务详情

1、JobDetail 对象是在将 job 注册到 scheduler 时,由客户端程序创建的,它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。

2、JobDetail 由 JobBuilder 创建/定义,Quartz 不存储 Job 的实际实例,但是允许通过使用 JobDetail 定义一个实例。

3、Job 有一个与其关联的名称和组,应该在单个 Scheduler 中唯一标识它们。

4、一个 Trigger(触发器) 只能对应一个 Job(任务),但是一个 Job 可以对应多个 Trigger。JobDetal 与 Trigger 一对多

Trigger - 触发器

1、Trigger 用于触发 Job 的执行。TriggerBuilder 用于定义/构建触发器实例。

2、Trigger也有一个相关联的 JobDataMap,用于给Job传递一些触发相关的参数。

3、Quartz自带了各种不同类型的 Trigger,最常用的主要是SimpleTrigger和CronTrigger。

JobDataMap

1、JobDataMap 实现了 JDK 的 Map 接口,可以以 Key-Value 的形式存储数据。

2、JobDetail、Trigger 实现类中都定义 JobDataMap 成员变量及其 getter、setter 方法,可以用来设置参数信息,Job 执行 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、配置数据源相关链接

quartz需要单据的数据库,所以需要单据创建一个库来给quartz使用,我新建了一个scheduler的库,详细信息官方参考quartz-scheduler/quartz

#1 保存已经触发的触发器状态信息DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;#2 存放暂停掉的触发器表表DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;#3 调度器状态表DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;#4 存储程序的悲观锁的信息(假如使用了悲观锁)DROP TABLE IF EXISTS QRTZ_LOCKS;#5 简单的触发器表DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;#6 存储两种类型的触发器表DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;#7 定时触发器表DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;#8 以blob 类型存储的触发器DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;#9 触发器表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));#触发器表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));#定时触发器表,存储 cron trigger,包括 cron 表达式和时区信息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两种类型的触发器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 类型存储的触发器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));#存放暂停掉的触发器表表CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS  (    SCHED_NAME VARCHAR(120) NOT NULL,    TRIGGER_GROUP  VARCHAR(200) NOT NULL,    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP));# 存储与已触发的 trigger 相关的状态信息,以及相联 job 的执行信息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、 调度器状态表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、创建配置文件

Quartz 使用一个名为 quartz.properties 的属性文件进行信息配置,必须位于 classpath 下,是 StdSchedulerFactory 用于创建 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 中的所有数据,比如作业和触发器的信息都保存在内存中(而不是数据库中)            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            #线程数            threadCount: 10            #优先级            threadPriority: 5            #线程继承上下文类加载器的初始化线程            threadsInheritContextClassLoaderOfInitializingThread: true    #数据库方式    job-store-type: JDBC    #初始化表结构    jdbc:      initialize-schema: NEVER复制代码
4、新建一个任务实体类JobInfo,用户新建,传递任务信息

jobName:任务名称

jobGroup:任务组

jsonParams:任务执行信息(在用户定时远程调用接口的时候,我们可以接口信息封装到这个Map中)

cron:定时任务的cron表达式

timeZoneId:定制执行任务的时区

triggerTime:定时器时间(目前没用上)

@Datapublic class JobInfo {    private String jobName;    private String jobGroup;    private Map<String, Object> jsonParams;    private String cron;    private String timeZoneId;    private Date triggerTime; }复制代码
5、新建一个任务执行类HttpRemoteJob 实现 Job接口,重写execute()方法

execute()里面就是任务的逻辑:

① 使用HttpURLConnection发送网络请求,利用BufferedReader接收请求返回的结果

② 在任务的Description中取出定时请求的接口信息,解析Description,获取请求url

③ 编辑请求信息,通过URL类编辑请求信息,使用HttpURLConnection发送请求,并接收请求返回的状态码,根据状态码,判断是否请求成功,请求成功便通过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;@DisallowConcurrentExecutionpublic class HttpRemoteJob implements Job {   //日志    private static final Logger log = LoggerFactory.getLogger(HttpRemoteJob.class);   @Override    public void execute(JobExecutionContext context)throws JobExecutionException {      //用于发送网络请求      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");         //发送请求         connection.connect();         //获取请求返回的状态吗         int statusCode = connection.getResponseCode();         if (statusCode != 200){            //请求失败抛出异常            throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");         }         //如果返回值正常,数据在网络中是以流的形式得到服务端返回的数据         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定时任务默认是并发执行的,不会等待上一次任务执行完毕,只要有间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。@DisallowConcurrentExecution 这个注解是加在Job类上的,是禁止并发执行多个相同定义的JobDetail, , 但并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义), 但是可以同时执行多个不同的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内容        TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey();        //触发器名称        String triggerName = triggerKey.getName();        //出发组名称(带路径)        String triggerPathName = jobExecutionContext.getTrigger().getClass().getName();        //触发器类名称        String triggerClassName = jobExecutionContext.getTrigger().getClass().getSimpleName();    }复制代码

注意:

      //解析Description,获取请求url      JSONObject jsonObj= (JSONObject) JSONObject.parse(jsonParams);      String callUrl = jsonObj.getString("callUrl");复制代码

之前我们封装JobInfo信息是将Map<String, Object> jsonParams保存接口请求信息,们去解析接口请求的时候也要用callUrl去取,那么在封装JobInfo类时,Map中就需要指定key是callUrl,不然取不到就会报错了。

6、创建定时任务业务层,创建 JobService接口和 JobServiceImpl实现类

JobServiceImpl业务逻辑:

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();复制代码

③ 创建触发器,触发器有多种类型,需要定时执行就使用cron表达式,创建cron调度器建造器CronScheduleBuilder,再创建Trigger触发器,调度器建造器有很多种,除了我下面用到的简单调度器构造器SimpleTrigger,还有:

CalendarIntervalScheduleBuilder : 每隔一段时间执行一次(年月日)

DailyTimeIntervalScheduleBuilder : 设置年月日中的某些固定日期,可以设置执行总次数

以后我们再单独写一片介绍;

CronScheduleBuilder和SimpleTrigger的区别在于:CronScheduleBuilder是通过cron表达式定时某个时间点或多个时间点定时直接,而SimpleTrigger是周期性执行,着重与时间间隔,着重与周期执行;

④ 将任务添加到Scheduler中

此外还有一些Scheduler的其他方法:

获取任务触发器 :TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);

停止触发器 :scheduler.pauseTrigger(triggerKey);

移除触发器:scheduler.unscheduleJob(triggerKey);

删除任务:scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup));

根据jobName,jobGroup获取jobKey 恢复任务:scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup));

根据jobName,jobGroup获取jobKey 暂停任务: scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup));

根据jobName,jobGroup获取jobKey 立即执行任务: scheduler.triggerJob(JobKey.jobKey(jobName,jobGroup));

在下面的实现类代码中有很好的用例;

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);    /**     * 立即执行任务一主要是用于执行一次任务的场景     * @param jobName 任务名称     * @param jobGroup 任务组     */    void trigger(String jobName,  String jobGroup);}复制代码

JobServiceImpl实现类

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 **/@Servicepublic 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();        //根据cron,TimeZone时区,指定执行计划        CronScheduleBuilder builder = CronScheduleBuilder.cronSchedule(jobInfo.getCron())                .inTimeZone(TimeZone.getTimeZone(jobInfo.getTimeZoneId()));        //触发器        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());  //作业名称及其组名        //判断是否有相同的作业        try {            JobDetail jobDetail = scheduler.getJobDetail(jobKey);            if(jobDetail != null){                scheduler.deleteJob(jobKey);            }        } catch (SchedulerException e) {            e.printStackTrace();        }        //定义作业的详细信息,并设置要执行的作业类名,设置作业名称及其组名        JobDetail jobDetail = JobBuilder.newJob(HttpRemoteJob.class)                .withDescription(JsonUtils.object2Json(jobInfo.getJsonParams()))                .withIdentity(jobKey)                .build()                ;        //简单触发器,着重与时间间隔        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) {        //获取任务触发器        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);        try {            //停止触发器            scheduler.pauseTrigger(triggerKey);            //移除触发器            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 {            //根据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 {            //根据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 {            //根据jobName,jobGroup获取jobKey 立即执行任务            scheduler.triggerJob(JobKey.jobKey(jobName,jobGroup));        } catch (SchedulerException e) {            System.out.println(e.getMessage());        }    }}复制代码

到此为止,我的定时调用接口任务已经完成了,现在我们写个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");  //时区指定上海        jobInfo.setCron("0/5 * * * * ? ");  //每5秒执行一次        Map<String, Object> params = new HashMap<>();        //添加需要调用的接口信息        String callUrl = ";;        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秒就被任务调用了一个,这时候我们去看数据库中的scheduler库的QRTZ_JOB_DETAILS表和QRTZ_TRIGGERS表,可以看到我们我们定时任务的信息,表示我们的定时任务持久化成功,这个时候你关掉服务器,再重启任务也会定时去执行runTest接口;

然后我们再调用deleteTest接口删除任务

此时再看去看数据库中的scheduler库的QRTZ_JOB_DETAILS表和QRTZ_TRIGGERS表中就没有之前的任务数据了

标签: #quartzcom