项目概要:公司需要在应用系统中构建可供配置的任务定时系统 需要根据用户配置的时间节点来对应触发不同的定时任务,需要基于Qurzt2.0和Cron表达式开发
效果图如下:(原创设计 盗用必究)
实现流程:
1、考虑需要可供任务配置,必须使用Quartz的持久化任务系统,这里我基于Mysql去做任务持久。首先需要添加maven依赖,这里我们公司是SpringBoot项目,所以用的已经集成好的maven
2、导入Quartz系统表
下载地址: https://www.quartz-scheduler.org/downloads/files/quartz-2.2.3-distribution.tar.gz
解压缩找到docs目录找到dbTables目录找到对应数据库的文件随后执行文件
3、需要配置Quartz参数(我是在yml直接配置的 也可以单独取出properties并读取)
具体参数配置详解请参照:
转载自:https://juejin.cn/post/7025256089260130318
4、创建任务持久实体(根据需求自定)并创建好实体类服务类等
5、创建Quartz工具类 这里代码我自定义了一些
//Springboot已经为我们自动装配了任务调度器Scheduler, //无需额外配置便可以注入使用,由Springboot为我们管理调度器 @Autowired private Scheduler scheduler; @Autowired private QuartzBeanService quartzBeanService; /** * 当前Job是否存在 * * @param jobKey 键 * @return int */ @Override public int isJobExist(JobKey jobKey) { int result = 1; try { JobDetail jobDetail = scheduler.getJobDetail(jobKey); List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); if (jobDetail != null && triggers.size() > 0) result = 1; else if (jobDetail != null && triggers.size() == 0) result = 0; else result = -1; } catch (SchedulerException e) { result = -1; e.printStackTrace(); } return result; } /** * 恢复任务调度 * * @param jobName 任务名称 * @param jobGroup 任务组 * @return boolean */ @Override public boolean resumeJob(String jobName, String jobGroup) { boolean result = true; try { scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup)); } catch (SchedulerException e) { result = false; log.error(e.getMessage()); } return result; } /** * 暂停或恢复工作 * * @param quartzBean 任务持久实体 * @return boolean */ @Override public boolean pauseOrResumeJob(QuartzBean quartzBean) { boolean result = true; try { //获取状态 来判断是暂停还是恢复 注意获取trigger是通过触发器名/获取 if (!"PAUSED".equals(scheduler.getTriggerState(TriggerKey.triggerKey(quartzBean.getTriggerName(),quartzBean.getGroupTrigger())).name())){ scheduler.pauseJob(JobKey.jobKey(quartzBean.getJobName(),quartzBean.getGroupJob())); //更改任务状态 quartzBean.setStatus(2); quartzBeanService.updateById(quartzBean); }else{ //否则就恢复任务 scheduler.resumeJob(JobKey.jobKey(quartzBean.getJobName(),quartzBean.getGroupJob())); quartzBean.setStatus(1); quartzBeanService.updateById(quartzBean); } } catch (SchedulerException e) { result = false; log.error(e.getMessage()); } return result; } /** * 更新任务触发时间 * * @param quartzBean 任务持久实体 * @return boolean */ @Override public boolean reScheduleJob(QuartzBean quartzBean) { //判断当前状态 boolean result = true; try { //需要先判断当前任务是否为暂停(更新任务会自动启动当前暂停的任务) if ("PAUSED".equals(scheduler.getTriggerState(TriggerKey.triggerKey(quartzBean.getTriggerName(),quartzBean.getGroupTrigger())).name())){ //更改任务状态 quartzBean.setStatus(1); quartzBeanService.updateById(quartzBean); } CronTrigger cronTriggerOld = (CronTrigger)scheduler.getTrigger(TriggerKey.triggerKey(quartzBean.getTriggerName(),quartzBean.getGroupTrigger())); if (!cronTriggerOld.getCronExpression().equals(quartzBean.getCronExpression())){ CronTrigger cronTriggerNew = TriggerBuilder.newTrigger().withIdentity(quartzBean.getTriggerName(),quartzBean.getGroupTrigger()) .withSchedule(CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression())) .build(); scheduler.rescheduleJob(TriggerKey.triggerKey(quartzBean.getTriggerName(),quartzBean.getGroupTrigger()),cronTriggerNew); } } catch (SchedulerException e) { result = false; log.error(e.getMessage()); } return result; } /** * 删除工作 * * @param jobName 任务名 * @param jobGroup 任务组 * @return boolean */ @Override public boolean deleteJob(String jobName, String jobGroup) { boolean result = true; try { List<? extends Trigger> triggers = scheduler.getTriggersOfJob(JobKey.jobKey(jobName,jobGroup)); if (triggers.size() > 0) { if (!"PAUSED".equals(scheduler.getTriggerState(TriggerKey.triggerKey(jobName,jobGroup)).name())) scheduler.pauseTrigger(TriggerKey.triggerKey(jobName,jobGroup)); scheduler.unscheduleJob(TriggerKey.triggerKey(jobName,jobGroup)); } scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup)); } catch (SchedulerException e) { result = false; log.error(e.getMessage()); } return result; } /** * 添加工作 * * @param QuartzBean 持久实体 * @return {@code JsonResult} */ @Override public JsonResult addJob(QuartzBean QuartzBean) { int isJobExist = this.isJobExist(JobKey.jobKey(QuartzBean.getJobName(),QuartzBean.getGroupJob())); if (isJobExist == 1) { return JsonResult.error().setMsg("当前任务已存在在定时任务中!"); } else { try { JobDetail jobDetail = null; if (isJobExist == 0) { //任务组已存在 不需要创建新组 jobDetail = scheduler.getJobDetail(JobKey.jobKey(QuartzBean.getJobName(),QuartzBean.getGroupJob())); }else if (isJobExist == -1) { //没有任何任务存在 jobDetail = JobBuilder.newJob( (Class<? extends QuartzJobBean>)Class.forName(QuartzBean.getJobClass())) .withIdentity(QuartzBean.getJobName(),QuartzBean.getGroupJob()) .usingJobData("KingDeeId", QuartzBean.getRelevancyKingDeeDisposition()) .storeDurably().build(); } // 设置定时任务执行方式 这里采用Cron配置时间触发 也可用SimpleScheduleBuilder 两者区别于分别适用复杂与简单 CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withIdentity(QuartzBean.getTriggerName(),QuartzBean.getGroupTrigger()) .withSchedule(CronScheduleBuilder.cronSchedule(QuartzBean.getCronExpression())) .build(); scheduler.scheduleJob(jobDetail,cronTrigger); //创建全局监听器以便控制其他逻辑操作 scheduler.getListenerManager().addJobListener(new QuartzJobListener(), EverythingMatcher.allJobs()); }catch (ClassNotFoundException e) { return JsonResult.error().setMsg("任务对应的Class类不存在"); } catch (SchedulerException e) { return JsonResult.error().setMsg("任务调度失败"); } return JsonResult.ok().setMsg("定时任务创建成功!"); } }
6、需要创建任务执行时的页面逻辑(定时任务需要干什么)
注意:某个定时任务需要执行某个任务是根据你的类包路径匹配的
这里并不是固定 后期可以配置:xx任务触发xx类里面的执行逻辑
7(可选)、这里我添加了任务的监听器以便后续拓展并在项目重新启动的时候初始化我们的监听器,如果不初始化监听器的话,只有在你新增任务的时候才会添加监听器,当项目挂掉或者重启,之前的监听器则不会工作,
(1):创建监听器类
//全局Job监听器 @Component public class QuartzJobListener implements JobListener { //获取该JobListener的名称 @Override public String getName() { return getClass().getSimpleName(); } //Scheduler在JobDetail即将被执行,但又被TriggerListerner否决时会调用该方法 @Override public void jobToBeExecuted(JobExecutionContext jobExecutionContext) { System.out.println("这里处理时间触发器否决任务时的逻辑"); } //Scheduler在JobDetail将要被执行时调用这个方法。 @Override public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) { //例如执行前告知xxx System.out.println("这里是Job即将执行之前 可以处理逻辑"); } //Scheduler在JobDetail被执行之后调用这个方法 @Override public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) { //例如执行后发送邮件告知xxx System.out.println("这里是Job即将执行之后 可以处理逻辑"); } }
(2):在添加任务时添加进入我们的监听器
(3):初始化添加监听器
这里考虑使用Spring的ApplicationListener
接口,监听ContextRefreshedEvent
事件,当Spring容器启动完成时,自动将任务监听器注册到Quartz Scheduler中。需要注意的是,Scheduler
和你的监听器
都需要通过Spring的依赖注入来获取。
注意:
1、需要注意的是配置文件中的线程池需要根据需求自定,不然任务过多时线程池数量过小会导致我们的定时任务不工作
2、这里定时任务的时间配置并非一定是Cron,这里使用Cron是因为有较复杂的时间场景,如果简单也可以使用Simple来作为时间触发
3、获取TriggerKey必须是Trigger的name与Group,踩坑过 用的是jobname和jobgrouop获取一直获取不到,但是他是不会报错的
4、另外quartz内置实现了不同于负载均衡的持久任务调度:
在Quartz中,多个实例都是基于相同的Job存储库运行的,也就是说所有的实例都共享相同的Job定义和任务调度计划。当其中一个实例挂机或停止时,该实例持有的正在运行的任务将被暂停或终止。为了保证调度服务的高可用性和容错性,Quartz允许在不同的节点上运行多个实例,以确保即使某个实例发生故障,其他实例也能够继续执行调度任务。
当某个实例停止或挂机时,其他实例会检测到并尝试接管其正在运行的任务。这是通过Quartz内置的集群机制实现的,Quartz允许多个调度实例通过数据库实现任务的共享和调度。每个实例都在Job存储库中记录其当前状态,如当前正在运行的Job和任务调度计划等。因此,当一个实例挂机时,其他实例可以检查数据库中的Job存储库,找到该实例已经运行的Job和调度计划,然后接管它们的执行,从而实现高可用性和容错性。
这里可以进行自测以下是我的自测记录,简要说明一下
这里字段代表的 调度器名称(yml配置的)实例名称 最后检查时间 检查时间 (纯靠自己翻译,仅供参考)有一台实例项目就自动有一条数据,目前是公司两台电脑 都是本地运行环境 代码统一,数据库共享的一个库,这时我模拟服务器挂掉,认为停止项目 这时候了另一台电脑会继续运行我们的Quartz调度任务。
至此,第一个基于springboot的quartz定时任务就已经构建成功了。目前公司已经测试完成并投入使用。
第一篇博客,如有不足 见谅。