我正在参与「启航方案」

一、基本概念

Quartz 是功能强大的开源作业调度库,几乎能够集成到任何 Java 应用程序中,从最小的独立应用程序到最大的电子商务系统。Quartz 可用于创立简略或杂乱的方案,以履行数以万计的工作;能够履行您编写的一切内容。

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

假如实践环境中守时使命过多,处理频频,建议习惯第三方封装的调度框架,因为守时器操作底层都是多线程的操作,使命的启动、暂停、康复、删去、实质是线程的启动、暂停、中止、唤醒等操作。

二、Quartz-scheduler 的中心流程

SpringBoot-集成Quartz实现持久化定时接口调用任务

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服务

SpringBoot-集成Quartz实现持久化定时接口调用任务
增加依靠:

<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

SpringBoot-集成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;
#8blob 类型存储的触发器
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:守时器时刻(目前没用上)

@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、新建一个使命履行类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;
@DisallowConcurrentExecution
public 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
 **/
@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();
        //依据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 = "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("使命已删去");
    }
}

四、验证成果

SpringBoot-集成Quartz实现持久化定时接口调用任务

SpringBoot-集成Quartz实现持久化定时接口调用任务

能够看到runTest接口每隔5秒就被使命调用了一个,这时分咱们去看数据库中的scheduler库的QRTZ_JOB_DETAILS表和QRTZ_TRIGGERS表,能够看到咱们咱们守时使命的信息,表明咱们的守时使命持久化成功,这个时分你关掉服务器,再重启使命也会守时去履行runTest接口;

SpringBoot-集成Quartz实现持久化定时接口调用任务

SpringBoot-集成Quartz实现持久化定时接口调用任务

然后咱们再调用deleteTest接口删去使命

SpringBoot-集成Quartz实现持久化定时接口调用任务

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

end~~~