【微服务】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表达式中各个位置的元素代表的含义如下
Seconds | Minutes | Hours | DayofMonth | Month | DayofWeek |
---|---|---|---|---|---|
秒 | 分钟 | 小时 | 日期天/日 | 日期月份 | 星期 |
3.2 Cron表达式各元素说明
cron表达式中各个位置的元素的用法参考如下
时间元素 | 可出现的字符 | 有效数值范围 |
---|---|---|
Seconds | , - * / | 0-59 |
Minutes | , - * / | 0-59 |
Hours | , - * / | 0-23 |
DayofMonth | , - * / ? L W | 0-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/datamkdir -p /usr/local/mysql/logsmkdir -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:sstime-zone: GMT+8datasource:url: jdbc:mysql://IP:3306/quartz_db?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&useSSL=falseusername: rootpassword: 密码driver-class-name: com.mysql.jdbc.Driverjta:atomikos:properties:recovery:forget-orphaned-log-entries-delay:
#mybatis plus 设置
mybatis-plus:mapper-locations: classpath:resources/mapper/*global-config:# 关闭MP3.0自带的bannerbanner: falsedb-config:#主键类型 0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";table-underline: truelogic-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: truelog-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 {@Autowiredprivate QuartzJobService quartzJobService;@Autowiredprivate 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;@Autowiredprivate Scheduler scheduler;/*** 保存&启动定时任务*/@Overridepublic 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);}/*** 恢复定时任务*/@Overridepublic 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);}@Overridepublic void test(String param) {System.out.println("param====>"+param);}/*** 编辑&启停定时任务* @throws SchedulerException*/@Overridepublic 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);}/*** 删除&停止删除定时任务*/@Overridepublic 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表达式构建一个新的triggerCronTrigger 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 {/*** 分页*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));// 注册乐观锁 插件return mybatisPlusInterceptor;}}
MyMetaObjectHandler,统一处理时间日期等字段信息
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {this.setFieldValByName("createTime",new Date(),metaObject);this.setFieldValByName("updateTime",new Date(),metaObject);}@Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName("updateTime",new Date(),metaObject);}
}
5.4.4 job执行任务类
在实际开发中,具体执行各类业务时,需要根据需求场景编写任务执行类,下面提供两个测试使用的任务类
不带参数的任务
@Slf4j
public class SampleJob implements Job {@Overridepublic 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 {@Autowiredprivate QuartzJobService quartzJobService;/*** 若参数变量名修改 QuartzJobController中也需对应修改*/private String parameter;public void setParameter(String parameter) {this.parameter = parameter;}@Overridepublic 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整体来说学习成本不算高,而且适用的场景也比较多,在项目中合理的引用,可以灵活的解决很多问题。
相关文章:

【微服务】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表达…...

Electron+Ts+Vue+Vite桌面应用系列:TypeScript常用时间处理工具
文章目录 1️⃣ 时间处理工具1.1 格式化时间1.2 把时间戳改成日期格式1.3 Day.js 工具类使用1.4 date-fns 工具类使用 优质资源分享 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/134712978 ElectronTsVueVite桌面应用…...

记录 | centos源码编译bazel
tensorflow的源码编译依赖于 bazel 这里进行 bazel 的源码编译 1、安装依赖 sudo yum install -y java-11-openjdk sudo yum install -y java-11-openjdk-devel sudo yum install -y protobuf-compiler zip unzip2、知悉要安装的 bazel 的版本 务必安装受支持的 Bazel 版本…...

常见的Bean工厂后置处理器
此代码在jdk11上测试通过,SpringBoot版本为2.7.14 1.上代码 导入坐标 <dependencies><!-- spring数据坐标 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-rest</art…...
代码随想录算法训练营第四十二天| 416 分割等和子集
目录 416 分割等和子集 416 分割等和子集 class Solution { public:const int N 210;bool canPartition(vector<int>& nums) {vector<int>f(N);int sum 0;for(auto num : nums)sum num;if(sum % 2 1)return false;//如果int target sum / 2;for(int i …...

memmove 和 memcpy的区别
函数原型及作用 memcpy 和 memmove 都是C语言中的库函数,在头文件string.h中,作用是拷贝一定长度的内存的内容,原型分别如下: void* memcpy(void *dst, const void *src, size_t count); void* memmove(void *dst, const void *…...
C实现的双向链表队列
如下代码所示,一个头文件实现的双向链表,用c代码实现: #ifndef _LINUX_LIST_H #define _LINUX_LIST_H#include "stddef.h" #include "poison.h"#ifndef ARCH_HAS_PREFETCH #define ARCH_HAS_PREFETCH static inline voi…...

自适应中值滤波器的python代码实现-----冈萨雷斯数字图像处理
基本原理: 自适应中值滤波器是一种图像处理技术,用于去除图像中的噪声。其原理是根据像素周围邻域内像素值的特性,动态地选择滤波器的大小和中值滤波的程度。 **邻域选择:**对于每个像素点,选取一个窗口或者邻域&…...
Python作业答疑_6.22~6.25
一、Python 一班 1. 基数分割列表 1.1 问题描述 给定一无序数列,把数列的第一个数字当成基数,让数列中基数小的数字排在数列前面,比基数大的数字排在数列的后面。 1.2 问题示例 如数列:num[4,1,8,3,9,2,10,7]。基数为 4&…...

Uber Go 语言编码规范
uber-go/guide 的中文翻译 English 文档链接 Uber Go 语言编码规范 Uber 是一家美国硅谷的科技公司,也是 Go 语言的早期 adopter。其开源了很多 golang 项目,诸如被 Gopher 圈熟知的 zap、jaeger 等。2018 年年末 Uber 将内部的 Go 风格规范 开源到 G…...
UniRepLKNet:用于音频、视频、点云、时间序列和图像识别的通用感知大内核ConvNet
摘要 https://arxiv.org/abs/2311.15599 大核卷积神经网络(ConvNets)最近受到了广泛的研究关注,但存在两个未解决的关键问题需要进一步研究。(1)现有大核ConvNets的架构在很大程度上遵循传统ConvNets或Transformers的设计原则,而大核ConvNets的架构设计仍未得到充分解决。(2…...

Http协议与Tomcat
HTTP协议 HTTP协议(HyperText Transfer Protocol)即超文本传输协议 ,是TCP/IC网络体系结构应用层的一个客户端-服务端协议,是所有客户端,服务端数据传输的基石(数据传输规则) 特点 ⭐基于TCP协…...

Spring AOP从入门到精通
目录 1. AOP的演化过程 1. 代理模式 2. 动态代理 2.1 JDK动态代理 2.2 Cglib动态代理 3. Spring模式 3.1 ProxyFactory 3.2 ProxyFactoryBean 3.3 AbstractAutoProxyCreator 2. Spring AOP抽象 1. 核心术语 1.1 连接点(JoinPoint) 1.2 切点(Pointcut) 1.3 增强(Ad…...

Tap虚拟网卡
1 概述 Tap设备通常用于虚拟化场景下,其驱动代码位于drivers/net/tun.c,tap与tun复用大部分代码, 注:drivers/net/tap.c并不是tap设备的代码,而是macvtap和ipvtap; 下文中,我们统一称tap&#…...

【数电笔记】53-与非门构成的基本RS触发器
目录 说明: 1. 电路组成 2. 逻辑功能 3. 特性表 4. 特性方程 5. 状态转换图 6. 驱动表 7. 例题 例1 例2 说明: 笔记配套视频来源:B站;本系列笔记并未记录所有章节,只对个人认为重要章节做了笔记;…...

kubernetes(k8s)容器内无法连接同所绑定的Service ClusterIP问题记录
kubernetes(k8s)容器内无法连接同所绑定的Service ClusterIP问题记录 1. k8s环境 k8s使用kubernetes-server-linux-amd64_1.19.10.tar.gz 二进制bin 的方式手动部署 k8s 版本: [rootmaster ~]# kubectl version Client Version: version.Info{Major:"1", Minor:&…...

Hadoop入门学习笔记
视频课程地址:https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接:https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 这里写目录标题 一、VMware准备Linux虚拟机1.1. VMware安装Linux虚拟机1.1.1. 修改虚拟机子网IP和网关1.1.2. 安装…...

堆栈,BSS,DATA,TEXT
一、目标文件 首先目标文件的构成,Linux下就是.o 文件 编译器编译源码后生成的文件叫目标文件(Object File)。 目标文件和可执行文件一般采用同一种格式,这种存储格式为 ELF。 目前文件的内容至少有编译后的机器指令代码和数据&a…...
Java八股文面试全套真题【含答案】-JSON篇
什么是JSON? 答案:JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,基于JavaScript的对象字面量表示法,用于在不同语言和平台之间传输数据。JSON的数据结构是怎样的? 答案…...

数据库管理-第119期 记一次迁移和性能优化(202301130)
数据库管理-第119期 记一次迁移和性能优化(202301130) 1 迁移 之前因为DV组件没有迁移成功的那个PDB,后来想着在目标端安装DV组件迁移,结果目标端装不上,而且开了SR也没看出个所以然来。只能换一个方向,尝…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...