在选择技术栈之前,一定要先明确一件事情,你真的需要用它吗?还有其他方式可以使用吗?
相比其他技术技术,优点在哪里呢?使用了之后的利与弊等等。
写这个主要是因为一直想写一下定时任务这个主题,这个算是写那篇文前期的铺垫和基础吧~
本文没有聊到 Java
其他的实现定时任务的方法啥的~,只是对使用 Quartz 做了一个小实践
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 其实就是通过一个调度线程不断的扫描数据库中的数据来获取到那些已经到点要触发的任务,然后调度执行它的。这个线程就是 QuartzSchedulerThread类。其run方法中就是quartz的调度逻辑。
另外,这是一个Demo,木有考虑并发、多任务执行等等状态的发生及处理情况,见谅。
Quartz 的几个核心概念
Job
表示一个工作,要执行的具体内容。此接口中只有一个方法
void execute(JobExecutionContext context) 复制代码
JobDetail表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
Trigger代表一个调度参数的配置,什么时候去调。
Scheduler代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
基本步骤就那些,这篇也不是高大上讲原理和流程之类的,就是偏向实操,可能一些地方在代码中含有注释,就不再贴说明了~
基本:JDK 8、SpringBoot、MybatisPlus、Quartz
创建一个 SpringBoot 项目
导入相关依赖~
org.springframework.boot spring-boot-starter-parent 2.5.2 1.8 8 8 复制代码 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-quartz org.projectlombok lombok mysql mysql-connector-java com.baomidou mybatis-plus-boot-starter 3.4.1 cn.hutool hutool-all 5.1.4 com.alibaba fastjson 1.2.76
项目结构:
找到 quartz 需要的 sql 文件,在数据库中执行,这也是Quartz持久化的基础~
往下滑,找到你需要的sql文件即可。
执行完的结果:
在此基础上,我们再额外增加一张表,与我们可能有业务关联的信息整合,这啥啥允许为空,是方便我写测试~,并非正例
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; 复制代码
我们直接从controller说起吧,从上往下开发。
其实一旦牵扯到表的操作,我们无疑就是crud四件事。
/** * @Description: 定时任务在线管理 * @author nzc */ @RestController @RequestMapping("/quartzJob") @Slf4j public class QuartzJobController { @Autowired private IQuartzJobService quartzJobService; @Autowired private Scheduler scheduler; @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, HttpServletRequest req) { Pagepage = new Page (pageNo, pageSize); IPage pageList = quartzJobService.page(page); return Result.ok(pageList); } @RequestMapping(value = "/add", method = RequestMethod.POST) public Result> add(@RequestBody QuartzJob quartzJob) { List list = quartzJobService.findByJobClassName(quartzJob.getJobClassName()); if (list != null && list.size() > 0) { return Result.error("该定时任务类名已存在"); } quartzJobService.saveAndScheduleJob(quartzJob); return Result.ok("创建定时任务成功"); } @RequestMapping(value = "/edit", method = RequestMethod.PUT) public Result> eidt(@RequestBody QuartzJob quartzJob) { try { quartzJobService.editAndScheduleJob(quartzJob); } catch (SchedulerException e) { log.error(e.getMessage(),e); return Result.error("更新定时任务失败!"); } return Result.ok("更新定时任务成功!"); } @RequestMapping(value = "/delete", method = RequestMethod.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("删除成功!"); } @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 */ @GetMapping(value = "/pause") public Result
public interface IQuartzJobService extends IService{ List findByJobClassName(String jobClassName); boolean saveAndScheduleJob(QuartzJob quartzJob); boolean editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException; boolean deleteAndStopJob(QuartzJob quartzJob); boolean resumeJob(QuartzJob quartzJob); } 复制代码
其中最主要的实现都是在这里:
@Slf4j @Service public class QuartzJobServiceImpl extends ServiceImplimplements IQuartzJobService { @Autowired private QuartzJobMapper quartzJobMapper; @Autowired private Scheduler scheduler; @Override public List findByJobClassName(String jobClassName) { return quartzJobMapper.findByJobClassName(jobClassName); } /**保存&启动定时任务*/ @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); } /**编辑&启停定时任务 @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()); } /** 添加定时任务*/ 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 MyException("创建定时任务失败", e); } catch (RuntimeException e) { throw new MyException(e.getMessage(), e); }catch (Exception e) { throw new MyException("后台找不到该类名:" + jobClassName, e); } } /** 删除定时任务*/ 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 MyException("删除定时任务失败"); } } private static Job getClass(String classname) throws Exception { Class> class1 = Class.forName(classname); return (Job) class1.newInstance(); } } 复制代码
@Mapper public interface QuartzJobMapper extends BaseMapper{ @Select("select * from sys_quartz_job where job_class_name = #{jobClassName}") public List findByJobClassName(@Param("jobClassName") String jobClassName); } 复制代码
@Data @TableName("sys_quartz_job") public class QuartzJob implements Serializable { @TableId(type = IdType.ID_WORKER_STR) private String id; private String createBy; private String updateBy; /**任务类名*/ private String jobClassName; /** cron表达式 */ private String cronExpression; /** 参数*/ private String parameter; private String description; /** 状态 0正常 -1停止*/ private Integer status; @TableLogic private Integer delFlag; @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(fill = FieldFill.INSERT) private Date createTime; @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; } 复制代码
另外在这里顺带补充一下,本项目在yml中配置quartz,如下:
spring: ## quartz定时任务,采用数据库方式 quartz: job-store-type: jdbc 复制代码
其他很杂的一些MybatisPlus 相关和一些公共类,从仓库中拿一下就好 github源码
如果调度器要执行任务,首先得要有一个任务相关滴类。
写了两个平常的案例,一个是不带参数的,一个是带参数的。
/** * 不带参的简单定时任务 * @Author nzc */ @Slf4j public class SampleJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.info(String.format("Ning zaichun的 普通定时任务 SampleJob ! 时间:" + new Date())); } } 复制代码
带参数的
/** * 带参数的简单的定时任务 * @Author nzc */ @Slf4j public class SampleParamJob implements Job { /** * 若参数变量名修改 QuartzJobController中也需对应修改 */ private String parameter; public void setParameter(String parameter) { this.parameter = parameter; } @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.info(String.format("welcome %s! Jeecg-Boot 带参数定时任务 SampleParamJob ! 时间:" + LocalDateTime.now(), this.parameter)); } } 复制代码
启动项目,让我们拿起来postman来测试吧,康康该如何使用,数据表在使用的时候,又有怎么样的变化~
我们直接来测试添加定时任务的接口,先来个不用参数的吧
{ "createBy": "nzc", "jobClassName": "com.nzc.quartz.job.SampleJob", "cronExpression": "0/10 * * * * ? ", "description": "每十秒执行一次", "status": "0" } 复制代码
添加完之后就已经在执行了。
此时我们将项目停止,重新启动~
调度器也会主动去检测任务信息,如果有,就会开始执行。
我们再来测测带有参数的
也是可以成功的,并且也是按照我们设定的时间执行的
其他的接口也是同样如此,根据接口的设定,将参数传入即可
源码
本文不牵扯到过多的内容,就是一篇普通的入门的使用教程。
后续在更文的时候,再打算说一说它的流程。
我觉得内部的执行流程和机制才是有趣的,Debug的时候,你会发现很多很多的奥妙~
不过想要探索之前,还是需要学会如何使用才行~
明天继续~,晚安,各位