【微服务】springboot整合quartz使用详解

目录

一、前言

二、quartz介绍

2.1 quartz概述

2.2 quartz优缺点

2.3 quartz核心概念

2.3.1 Scheduler

2.3.2 Trigger

2.3.3 Job

2.3.4 JobDetail

2.4 Quartz作业存储类型

2.5 适用场景

三、Cron表达式

3.1 Cron表达式语法

3.2 Cron表达式各元素说明

3.3 Cron表达式字符说明

四、环境准备

4.1 搭建步骤

4.1.1 拉取镜像

4.1.2 创建相关的数据目录

4.1.3 启动容器

五、springboot整合quartz

5.1 导入maven依赖

5.2 初始化数据表

5.3 增加配置文件

5.4 核心业务类

5.4.1 前端控制器类

5.4.2 业务实现类

5.4.3 mybatis-plus配置类

5.4.4 job执行任务类

5.5 效果测试

5.5.1 添加任务

5.5.2 暂停任务

5.6 补充说明

六、写在结尾


一、前言

在项目开发中,经常需要定时任务来处理一些业务,可以说定时任务的场景无处不在,比如定时同步数据,定时清理数据等,在使用SpringBoot框架中,简单的场景,可以考虑使用内置的注解就可以使用,但是如果需要调度的场景比较复杂,或者说需要更灵活的配置调度,就需要考虑采用其他的方案了,比如Quartz , xxl-job,elastic job等。

二、quartz介绍

2.1 quartz概述

Quartz 是 OpenSymphony 开源组织在 Job Scheduling 领域又一个开源项目,是完全由 Java 开发的一个开源任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中,它提供了巨大的灵活性而不牺牲简单性。

官网地址:Quartz Enterprise Job Scheduler

2.2 quartz优缺点

quartz具备如下优点:

  • 丰富的 Job 操作 API;
  • 支持多种配置;
  • SpringBoot 无缝集成;
  • 支持数据持久化;
  • 支持集群部署;

Quartz 还支持开源,是一个功能丰富的开源作业调度库,可以集成到几乎任何 Java 应用程序中;

缺点:

  • 学习成本:由于Quartz是一个相对复杂的框架,使用它需要一定的学习成本。开发人员需要熟悉其概念、配置和API,以正确地使用和管理任务调度;
  • 配置复杂性:Quartz的配置文件较为复杂,特别是在涉及到分布式环境或复杂任务依赖关系时。正确配置和管理Quartz调度器可能需要一定的技术知识和经验‘

 

2.3 quartz核心概念

在真正开始学习quartz之前,有必要对quartz中的几个核心业务概念做全面的了解。下面几个核心组件的关系如下:

2.3.1 Scheduler

Quartz 中的任务调度器,通过 Trigger 和 JobDetail 可以用来调度、暂停和删除任务,提供调度任务的主要API;

调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组及名称,组及名称是 Scheduler 查找定位容器中某一对象的依据,Trigger 的组及名称必须唯一,JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的);

2.3.2 Trigger

定义调度执行计划的组件,即什么时候触发执行,Trigger是Quartz 中的触发器,是一个类,描述触发 Job 执行的时间触发规则,主要有 SimpleTrigger 和 CronTrigger 这两个子类。

1)当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择;

2)CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的 16:00 ~16:10 执行调度等;

2.3.3 Job

Quartz 中具体的任务,包含了执行任务的具体方法,是一个接口,只定义一个方法 execute() 方法,在实现接口的 execute() 方法中编写所需要定时执行的 Job。

2.3.4 JobDetail

Quartz 中需要执行的任务详情,包括了任务的唯一标识和具体要执行的任务,可以通过 JobDataMap 往任务中传递数据。

2.4 Quartz作业存储类型

RAMJobStore

RAM 也就是内存,默认情况下 Quartz 会将任务调度存储在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失

JDBC 作业存储

存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理,随时停止、暂停、修改任务。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢

2.5 适用场景

在下面的场景中你可以考虑适用quartz

定时任务

Quartz最常见的应用场景是执行定时任务,比如每天凌晨生成报表、每小时执行数据备份等。它可以根据配置的时间表触发任务,并在预定的时间间隔内重复执行。

计划任务

除了定时任务,Quartz还支持基于日历的计划任务。例如,在特定的日期或周几执行某个任务,或者排除特定的日期和时间段。

分布式任务调度

Quartz可以与分布式系统集成,实现分布式任务调度。它提供了可靠的任务调度机制,能够确保在分布式环境中准确地调度和执行任务。

监控和管理

Quartz提供了监控和管理任务的功能。它可以获取任务的执行状态、日志和性能指标,并提供了对任务的管理接口,如启动、停止、暂停和恢复任务等。

三、Cron表达式

Cron 表达式是任务调度的关键要素,简单来说,Cron 表达式是一个字符串,包括6~7个时间元素,每个元素都有特定的含义,在 Quartz中可以用于指定任务的执行时间。比如:0/10 * * * * ?

3.1 Cron表达式语法

cron表达式中各个位置的元素代表的含义如下

SecondsMinutesHoursDayofMonthMonthDayofWeek
分钟小时日期天/日日期月份星期

3.2 Cron表达式各元素说明

cron表达式中各个位置的元素的用法参考如下

时间元素可出现的字符有效数值范围
Seconds, - * /0-59
Minutes, - * /0-59
Hours, - * /0-23
DayofMonth, - * / ? L W0-31
Month, - * /1-12
DayofWeek, - * / ? L #1-7或SUN-SAT

3.3 Cron表达式字符说明

字符作用举例
,列出枚举值在Minutes域使用5,10,表示在5分和10分各触发一次
-表示触发范围在Minutes域使用5-10,表示从5分到10分钟每分钟触发一次
*匹配任意值在Minutes域使用*, 表示每分钟都会触发一次
/

起始时间开始触发,每隔固定时间触发

一次

在Minutes域使用5/10,表示5分时触发一次,每10分钟再触发一次
?

在DayofMonth和DayofWeek中,用于匹

配任意值

在DayofMonth域使用?,表示每天都触发一次
#在DayofMonth中,确定第几个星期几1#3表示第三个星期日
L表示最后在DayofWeek中使用5L,表示在最后一个星期四触发
W表示有效工作日(周一到周五)在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日4日触发一次

在线 Cron 表达式生成器,其实 Cron 表达式无需多记,需要使用的时候直接使用在线生成器就可以了

地址1: 在线Cron表达式生成器

地址2: 在线Cron表达式生成器

四、环境准备

在实际使用quartz时,任务调度相关的配置数据肯定是要入库的,即Quartz作业存储类型使用jdbc作业存储,所以需要准备mysql环境,下面使用docker快速搭建mysql环境。

4.1 搭建步骤

4.1.1 拉取镜像

docker pull mysql:5.7

4.1.2 创建相关的数据目录

mkdir -p /usr/local/mysql/data

mkdir -p /usr/local/mysql/logs

mkdir -p /usr/local/mysql/conf

4.1.3 启动容器

docker run --name mysql -p 3306:3306 \
-v /usr/local/mysql/data:/var/lib/mysql \
-v /usr/local/mysql/logs:/logs \
-v /usr/local/mysql/conf:/etc/mysql/conf.d \
  -e MYSQL_ROOT_PASSWORD=密码 -d mysql:5.7

五、springboot整合quartz

网上关于quartz与springboot的整合方式有很多,但很多样例无法跑起来,请参选接下来的操作步骤,完成一个完整的整合。完整的工程目录如下:

5.1 导入maven依赖

以下为本例必须的依赖,可以根据自身的情况酌情添加

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
        <relativePath/>
    </parent>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.4</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

    </dependencies>

5.2 初始化数据表

上面导入完成依赖jar包之后,在依赖的jar包中,找到与调度任务初始化的一个sql

拷贝上面的sql文件中的初始化sql,在mysql中创建一个数据库,这里命名为:quartz_db,然后执行即可,执行成功后,可以看到与任务调度相关的数据表就初始化了

在此基础上,再额外增加一张表,与我们可能有业务关联的信息整合

DROP TABLE IF EXISTS `sys_quartz_job`;
 CREATE TABLE `sys_quartz_job`  (
   `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
   `create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
   `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
   `del_flag` int(1) NULL DEFAULT NULL COMMENT '删除状态',
   `update_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
   `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
   `job_class_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务类名',
   `cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'cron表达式',
   `parameter` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '参数',
   `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
   `status` int(1) NULL DEFAULT NULL COMMENT '状态 0正常 -1停止',
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

5.3 增加配置文件

配置application.yml文件

server:
  port: 8088
spring:
  ## quartz定时任务,采用数据库方式
  quartz:
    job-store-type: jdbc
  #json 时间戳统一转换
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    url: jdbc:mysql://IP:3306/quartz_db?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: 密码
    driver-class-name: com.mysql.jdbc.Driver
  jta:
    atomikos:
      properties:
        recovery:
          forget-orphaned-log-entries-delay:
#mybatis plus 设置
mybatis-plus:
  mapper-locations: classpath:resources/mapper/*
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
      table-underline: true
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 返回类型为Map,显示null对应的字段
    call-setters-on-nulls: true
    log-impl:   org.apache.ibatis.logging.stdout.StdOutImpl

5.4 核心业务类

下面列举出工程中核心的类文件,编写的顺序为从上至下,即从接口开始

5.4.1 前端控制器类

该类主要提供与交互操作相关的API接口,后续可以通过界面的配置方式操作任务

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.congge.common.CommonConstant;
import com.congge.common.Result;
import com.congge.entity.QuartzJob;
import com.congge.exception.BizException;
import com.congge.service.QuartzJobService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/job")
public class QuartzController {

    @Autowired
    private QuartzJobService quartzJobService;

    @Autowired
    private Scheduler scheduler;

    /**
     * 分页列表查询
     *
     * @return
     */
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public Result<?> queryPageList(QuartzJob quartzJob, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                   @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
        Page<QuartzJob> page = new Page<QuartzJob>(pageNo, pageSize);
        IPage<QuartzJob> pageList = quartzJobService.page(page);
        return Result.ok(pageList);

    }

    /**
     * 添加定时任务
     *
     * @param quartzJob
     * @return
     */
    @PostMapping("/add")
    public Result<?> add(@RequestBody QuartzJob quartzJob) {
        List<QuartzJob> list = quartzJobService.list(new QueryWrapper<QuartzJob>().eq("job_class_name", quartzJob.getJobClassName()));
        if (list != null && list.size() > 0) {
            return Result.error("该定时任务类名已存在");
        }
        quartzJobService.saveAndScheduleJob(quartzJob);
        return Result.ok("创建定时任务成功");
    }

    /**
     * 更新定时任务
     *
     * @param quartzJob
     * @return
     */
    @PostMapping("/edit")
    public Result<?> eidt(@RequestBody QuartzJob quartzJob) {
        try {
            quartzJobService.editAndScheduleJob(quartzJob);
        } catch (SchedulerException e) {
            log.error(e.getMessage(), e);
            return Result.error("更新定时任务失败!");
        }
        return Result.ok("更新定时任务成功!");
    }

    /**
     * 通过id删除
     *
     * @param id
     * @return
     */
    @GetMapping("/delete")
    public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
        QuartzJob quartzJob = quartzJobService.getById(id);
        if (quartzJob == null) {
            return Result.error("未找到对应实体");
        }
        quartzJobService.deleteAndStopJob(quartzJob);
        return Result.ok("删除成功!");

    }

    /**
     * 批量删除
     *
     * @param ids
     * @return
     */
    @RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
    public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
        if (ids == null || "".equals(ids.trim())) {
            return Result.error("参数不识别!");
        }
        for (String id : Arrays.asList(ids.split(","))) {
            QuartzJob job = quartzJobService.getById(id);
            quartzJobService.deleteAndStopJob(job);
        }
        return Result.ok("删除定时任务成功!");
    }

    /**
     * 暂停定时任务
     *
     * @param jobClassName
     * @return
     */
    @GetMapping(value = "/pause")
    public Result<Object> pauseJob(@RequestParam(name = "jobClassName", required = true) String jobClassName) {
        QuartzJob job = null;
        try {
            job = quartzJobService.getOne(new LambdaQueryWrapper<QuartzJob>().eq(QuartzJob::getJobClassName, jobClassName));
            if (job == null) {
                return Result.error("定时任务不存在!");
            }
            scheduler.pauseJob(JobKey.jobKey(jobClassName.trim()));
        } catch (SchedulerException e) {
            throw new BizException("暂停定时任务失败");
        }
        job.setStatus(CommonConstant.STATUS_DISABLE);
        quartzJobService.updateById(job);
        return Result.ok("暂停定时任务成功");
    }

    /**
     * 恢复定时任务
     *
     * @param jobClassName
     * @return
     */
    @GetMapping(value = "/resume")
    public Result<Object> resumeJob(@RequestParam(name = "jobClassName", required = true) String jobClassName) {
        QuartzJob job = quartzJobService.getOne(new LambdaQueryWrapper<QuartzJob>().eq(QuartzJob::getJobClassName, jobClassName));
        if (job == null) {
            return Result.error("定时任务不存在!");
        }
        quartzJobService.resumeJob(job);
        //scheduler.resumeJob(JobKey.jobKey(job.getJobClassName().trim()));
        return Result.ok("恢复定时任务成功");
    }

    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    @GetMapping("/queryById")
    public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
        QuartzJob quartzJob = quartzJobService.getById(id);
        return Result.ok(quartzJob);
    }
}

5.4.2 业务实现类

接口控制器的业务逻辑实现

import com.congge.common.CommonConstant;
import com.congge.entity.QuartzJob;
import com.congge.exception.BizException;
import com.congge.mapper.QuartzJobMapper;
import com.congge.service.QuartzJobService;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import lombok.extern.slf4j.Slf4j;


@Slf4j
@Service
public class QuartzJobServiceImpl extends ServiceImpl<QuartzJobMapper, QuartzJob> implements QuartzJobService {

//    @Autowired
//    private QuartzJobMapper quartzJobMapper;

    @Autowired
    private Scheduler scheduler;

    /**
     * 保存&启动定时任务
     */
    @Override
    public boolean saveAndScheduleJob(QuartzJob quartzJob) {
        if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
            // 定时器添加
            this.schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
        }
        // DB设置修改
        return this.save(quartzJob);
    }

    /**
     * 恢复定时任务
     */
    @Override
    public boolean resumeJob(QuartzJob quartzJob) {
        schedulerDelete(quartzJob.getJobClassName().trim());
        schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
        quartzJob.setStatus(CommonConstant.STATUS_NORMAL);
        return this.updateById(quartzJob);
    }

    @Override
    public void test(String param) {
        System.out.println("param====>"+param);
    }

    /**
     * 编辑&启停定时任务
     * @throws SchedulerException
     */
    @Override
    public boolean editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException {
        if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
            schedulerDelete(quartzJob.getJobClassName().trim());
            schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
        }else{
            scheduler.pauseJob(JobKey.jobKey(quartzJob.getJobClassName().trim()));
        }
        return this.updateById(quartzJob);
    }

    /**
     * 删除&停止删除定时任务
     */
    @Override
    public boolean deleteAndStopJob(QuartzJob job) {
        schedulerDelete(job.getJobClassName().trim());
        return this.removeById(job.getId());
    }

    /**
     * 添加定时任务
     *
     * @param jobClassName
     * @param cronExpression
     * @param parameter
     */
    private void schedulerAdd(String jobClassName, String cronExpression, String parameter) {
        try {
            // 启动调度器
            scheduler.start();

            // 构建job信息
            JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName).usingJobData("parameter", parameter).build();
            // 表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName).withSchedule(scheduleBuilder).build();
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            throw new BizException("创建定时任务失败", e);
        } catch (RuntimeException e) {
            throw new BizException(e.getMessage(), e);
        }catch (Exception e) {
            throw new BizException("后台找不到该类名:" + jobClassName, e);
        }
    }

    /**
     * 删除定时任务
     *
     * @param jobClassName
     */
    private void schedulerDelete(String jobClassName) {
        try {
            /*使用给定的键暂停Trigger 。*/
            scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName));
            /*从调度程序中删除指示的Trigger */
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName));
            /*从 Scheduler 中删除已识别的Job - 以及任何关联的Trigger */
            scheduler.deleteJob(JobKey.jobKey(jobClassName));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BizException("删除定时任务失败");
        }
    }

    private static Job getClass(String classname) throws Exception {
        Class<?> class1 = Class.forName(classname);
        return (Job) class1.newInstance();
    }

}

5.4.3 mybatis-plus配置类

MybatisPlusConfig,配置分页信息

@Configuration
public class MybatisPlusConfig {

    /**
     * 分页
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 注册乐观锁 插件
        return mybatisPlusInterceptor;
    }

}

MyMetaObjectHandler,统一处理时间日期等字段信息

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

5.4.4 job执行任务类

在实际开发中,具体执行各类业务时,需要根据需求场景编写任务执行类,下面提供两个测试使用的任务类

不带参数的任务

@Slf4j
public class SampleJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info(String.format("执行普通定时任务 SampleJob !  当前时间:" + new Date()));
    }

}

带任务参数

import com.congge.service.QuartzJobService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
@Slf4j
public class SampleParamJob implements Job {

    @Autowired
    private QuartzJobService quartzJobService;
    /**
     * 若参数变量名修改 QuartzJobController中也需对应修改
     */
    private String parameter;

    public void setParameter(String parameter) {
        this.parameter = parameter;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info(String.format("准备执行带参数定时任务 SampleParamJob !   时间:" + LocalDateTime.now(), this.parameter));
        quartzJobService.test(this.parameter);
    }

}

5.5 效果测试

5.5.1 添加任务

使用postman调用添加任务的接口

执行成功后,从控制台可以看到,任务已经在执行了

同时数据库也增加了一条数据

5.5.2 暂停任务

浏览器调用接口:localhost:8088/job/pause?jobClassName=com.congge.job.SampleJob

调用成功后,可以从控制台看到任务已经停止执行了

其他的测试用例可以参照上面的方式继续测试即可。

5.6 补充说明

基于上面的整合和效果验证,下面留待几个问题请进一步思考和探究

问题1:

如何让任务添加成功后延迟一段时间执行?

问题2:

 如何解决任务的并发执行?

问题3:

 两次执行任务的时间间隔太短怎么办?

六、写在结尾

相对其他的分布式任务调度框架,quartz整体来说学习成本不算高,而且适用的场景也比较多,在项目中合理的引用,可以灵活的解决很多问题。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/226435.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

封装PoiExcelUtils

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 为什么要封装PoiExcelU…

Openfire CVE-2023-32315(metasploit版)

step1&#xff1a;用docker搭建环境 Step2&#xff1a;docker查看映射端口 Step3&#xff1a;打开mysql和apache2&#xff0c;访问特定端口&#xff0c;然后靶标应用。 Step4&#xff1a;用metasploit进行攻击&#xff1a; 首先&#xff0c;打开metasploit&#xff0c;然…

Java (JDK 21) 调用 OpenCV (4.8.0)

Java 调用 OpenCV 一.OpenCV 下载和安装二.创建 Java Maven 项目三.其他测试 一.OpenCV 下载和安装 Open CV 官网 可以下载编译好的包&#xff0c;也可以下载源码自行编译 双击安装 opencv-4.8.0-windows.exe 默认为当前目录 安装即解压缩 根据系统位数选择 将 x64 目录下 op…

数据结构 | 查漏补缺之求叶子结点,分离链接法、最小生成树、DFS、BFS

求叶子结点的个数 参考博文&#xff1a; 树中的叶子结点的个数 计算方法_求树的叶子节点个数-CSDN博客 分离链接法 参考博文 数据结构和算法——哈希查找冲突处理方法&#xff08;开放地址法-线性探测、平方探测、双散列探测、再散列&#xff0c;分离链接法&#xff09;_线性…

Linux存储软件栈到底有多复杂,存储软件栈全景概览

从今天开始&#xff0c;我们将介绍一下Linux的存储软件栈&#xff0c;也称为IO栈。今天我们先从整体上了解一下Linux的整个IO栈&#xff0c;后续再深入介绍每个组件。我们很难说清楚Linux的存储软件栈到底有多复杂&#xff0c;但thomas-krenn的一张图可能可以给大家一个比较直观…

堆的相关时间复杂度计算(C语言)

目录 前言 建堆的时间复杂度 向上调整建堆的时间复杂度 向下调整建堆的时间复杂度 维护堆的时间复杂度 top K问题的时间复杂度 前言 在前面的三篇文章中我们成功的实现了堆排序的升降序以及基于堆的top K问题&#xff0c;现在我们来解决它们的时间复杂度问题 建堆的时间…

查看Linux的Ubuntu的版本

我的Ubuntu版本是 Jammy x86_64&#xff0c;即 Ubuntu 22.04.3 LTS&#xff0c;代号为"Jammy Jellyfish"&#xff0c;架构是 x86_64&#xff08;64位&#xff09;。

UEC++ 探索虚幻5笔记 day11

虚幻5.2.1探索 项目目录探索 C工程一定不能是中文路径&#xff0c;中文项目名&#xff0c;最好全部不要用中文&#xff0c;蓝图项目可以是中文浅浅创建一个空项目&#xff0c;讲解一下之前UE4没有讲解的项目目录文件的分布组成 .vs&#xff1a;文件夹一般是项目编译缓存文件夹…

linux安装mysql5.7(一遍过)

之前安装的时候遇到了很多问题&#xff0c;浪费了一些时间。整理出这份教程&#xff0c;照着做基本一遍过。 这是安装包: 链接&#xff1a;https://pan.baidu.com/s/1gBuQBjA4R5qRYZKPKN3uXw?pwd1nuz 1.下载安装包&#xff0c;上传到linux。我这里就放到downloads目录下面…

游戏盾的防御原理以及为什么程序类型更适合接入游戏盾。

游戏盾是一种专门用于游戏服务器的安全防护服务&#xff0c;旨在抵御各种网络攻击。它的原理主要包括以下几个方面&#xff1a; 流量清洗和过滤&#xff1a;游戏盾会对进入游戏服务器的流量进行实时监测、分析和过滤。它通过识别恶意流量和攻击流量&#xff0c;过滤掉其中的攻击…

多种混沌映射初始化种群MATLAB代码(开源代码)

五种混沌映射改进鲸鱼算法&#xff08;自行切换&#xff09;&#xff1a; clc clear close allSearchAgents_no30; % Number of search agents %种群数量Function_nameF1; % Name of the test function that can be from F1 to F23 (Table 1,2,3 in the paper) %使用方程的名…

【java设计模式】——代理设计模式,两种举例说明

代理设计模式 1.介绍 Spring 框架中AOP底层使用动态代理设计模式。通过学习动态代理设计模式可以很好的理解Spring框架AOP底层 代理模式&#xff08;Proxy&#xff09;是GoF23种设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象&#xff0c;而是通过调用代理&am…

浅谈https

1.网络传输的安全性 http 协议&#xff1a;不安全&#xff0c;未加密https 协议&#xff1a;安全&#xff0c;对请求报文和响应报文做加密 2.对称加密与非对称加密 2.1 对称加密 特点&#xff1a; 加解密使用 相同 秘钥 高效&#xff0c;适用于大量数据的加密场景 算法公开&a…

深入了解Java Duration类,对时间的精细操作

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概6000多字&#xff0c;预计阅读时间长需要5分钟。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&#x…

Linux安装mysq 8.0服务端和客户端(亲测保成功)

1. 查看当前是否有已经安装好的mysql,先卸载 # 命令 rpm -qa|grep -i mysql# 结果显示 mysql-community-libs-5.7.42-1.el7.x86_64 mysql-community-common-5.7.42-1.el7.x86_64 mysql-community-libs-compat-5.7.42-1.el7.x86_64 mysql57-community-release-el7-10.noarch my…

perl脚本获取Windows系统常用路径信息

windows系统常用的路径,比如临时目录、资源文件夹、字体保存目录、应用程序数据存放目录等等。在日常操作的时候寻找略有不便。这里用perl写一个脚本&#xff0c;并把这些目录信息格式化为json&#xff0c;方便查找。如下是perl代码&#xff1a; #! /usr/bin/perl use v5.14; …

中国科技巨头:集体变革中 /华为鸿蒙 4:功能、模型、生态三大升级 |魔法半周报

我有魔法✨为你劈开信息大海❗ 高效获取AIGC的热门事件&#x1f525;&#xff0c;更新AIGC的最新动态&#xff0c;生成相应的魔法简报&#xff0c;节省阅读时间&#x1f47b; &#x1f525;资讯预览 中国科技巨头&#xff1a;集体变革中 华为鸿蒙 4&#xff1a;功能、模型、生…

2002-2021年全国各地级市环境规制18个相关指标数据

2002-2021年全国各地级市环境规制18个相关指标数据 1、时间&#xff1a;2002-2021年 2、来源&#xff1a;城市年鉴 3、指标&#xff1a;行政区划代码、地区、年份、工业二氧化硫排放量(吨)、工业烟粉尘排放量(吨)、工业废水排放量(万吨)、工业废水排放达标量(万吨)、工业二氧…

华为OD机试 - 攀登者1(Java JS Python C)

题目描述 攀登者喜欢寻找各种地图,并且尝试攀登到最高的山峰。 地图表示为一维数组,数组的索引代表水平位置,数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如:[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0],代表如下图所示的地图,地图中有两个山脉位置分别为 1,2,3,4,5…

SpringSecurity6 | 修改默认用户

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏: MySQL学习 🥭本文内容:SpringSecurity6 | 修改默认用户 📚个人知识库 :知识库,欢迎大家访问 学习参考…