网站运营推广该如何做,县级网站建设培训会,网站后台使用培训,开发一个网站需要几个人Quartz简介 Job 表示一个工作#xff0c;要执行的具体业务内容。
JobDetail 表示一个具体的可执行的调度程序#xff0c;Job 是这个可执行程调度程序所要执行的内容#xff0c;另外 JobDetail 还包含了这个任务调度的方案和策略。
Trigger 代表一个调度参数的配置#xf…Quartz简介 Job 表示一个工作要执行的具体业务内容。
JobDetail 表示一个具体的可执行的调度程序Job 是这个可执行程调度程序所要执行的内容另外 JobDetail 还包含了这个任务调度的方案和策略。
Trigger 代表一个调度参数的配置什么时候去调。
Scheduler 代表一个调度容器一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合就可以被 Scheduler 容器调度了。
案例
背景
系统中有一张自定义的定时任务表需要实现动态的添加、修改、删除、启停定时任务等功能定时任务里包含了业务需要执行的按设置周期执行的代码
由于不想使用Quartz的数据存储功能所以下面实现里直接使用了这张自定义的表以及使用了Quartz的任务调度和触发功能
实现步骤
1. 引入依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-quartz/artifactIdversion2.7.18/version
/dependency2. 添加配置类
Configuration
public class ScheduleQuartzConfig {Beanpublic Scheduler scheduler() throws SchedulerException {Scheduler scheduler schedulerFactoryBean().getScheduler();return scheduler;}Beanpublic SchedulerFactoryBean schedulerFactoryBean() {Properties properties new Properties();properties.setProperty(org.quartz.threadPool.threadCount, 10);properties.setProperty(org.quartz.threadPool.threadNamePrefix,quartz_worker);SchedulerFactoryBean factory new SchedulerFactoryBean();factory.setSchedulerName(QUARTZ_SCHEDULER);factory.setQuartzProperties(properties);return factory;}
}3. 编写Job类
该类就是将来要定时执行的业务代码具体代码路径根据实际情况规划即可重点是继承 QuartzJobBean重写 executeInternal 方法
//锁定机制以确保在同一时间只有一个任务实例运行
DisallowConcurrentExecution
public class MyJob extends QuartzJobBeanprivate final static Logger logger LoggerFactory.getLogger(PushPvImitateDataJob.class);Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {//业务代码logger.info(开始执行业务代码了。。。);try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();} logger.info(业务代码执行完成了);}
}4. 编写定时任务表的实体类
ScheduleLog.java
Data
public class ScheduleLog {private Integer id;private String name; //任务名称private String jobClassName; //任务的实现类 如xxx.xxx.xx.MyJobprivate String cronExpression; //触发时机表达式比如每5秒执行一次 0/5 * * * * ?private Integer status; //状态:0 启动 1 禁用private String remark;private Long createTime;private Long updateTime;private Long lastTime; //上一次执行时间private Long nextTime; //下一次执行时间private String lastTimeText;private String nextTimeText;
}5. 创建管理定时任务的工具类
Component
public class ScheduleQuartzManage {private static final Logger logger LoggerFactory.getLogger(ScheduleQuartzManage.class);/*** 创建定时任务 定时任务创建之后默认启动状态** param scheduler: 调度器* param scheduleLog: 报告订阅对象* return: void**/public void createScheduleJob(Scheduler scheduler, ScheduleLog scheduleLog) throws SchedulerException {//获取到定时任务的执行类 必须是类的绝对路径名称//定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。
// Class? extends Job jobClass PushPvImitateDataJob.class;Class? extends Job jobClass null;try {jobClass (Class? extends Job) Class.forName(scheduleLog.getJobClassName());} catch (ClassNotFoundException e) {e.printStackTrace();}// 构建定时任务信息JobDetail jobDetail JobBuilder.newJob(jobClass).withIdentity(scheduleLog.getId().toString(),jobGroup).usingJobData(id,scheduleLog.getId()).build();// 设置定时任务执行方式CronScheduleBuilder scheduleBuilder CronScheduleBuilder.cronSchedule(scheduleLog.getCronExpression());// 构建触发器trigger// 如果已经有下一次时间就设置为下一次时间为触发时间CronTrigger trigger;if (!Objects.isNull(scheduleLog.getNextTime())) {Date date new Date(scheduleLog.getNextTime());trigger TriggerBuilder.newTrigger().startAt(date).withIdentity(scheduleLog.getId().toString(), jobGroup).withSchedule(scheduleBuilder).build();} else {trigger TriggerBuilder.newTrigger().withIdentity(scheduleLog.getId().toString(),jobGroup).withSchedule(scheduleBuilder).build();}scheduler.scheduleJob(jobDetail, trigger);// 设置下次执行时间//long nextTime trigger.getNextFireTime().getTime();//LocalDateTime nextTime DateUtil.dateToLocalDate(trigger.getNextFireTime());//logger.info(下次一执行时间:{},DateUtil.formatDateTime(new Date(nextTime)));//scheduleLog.setNextTime(nextTime);}/*** 根据任务名称暂停定时任务** param scheduler 调度器* param jobName 定时任务名称这里直接用ReportSubscribePO的Id* throws SchedulerException*/public void pauseScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {JobKey jobKey JobKey.jobKey(jobName, jobGroup);scheduler.pauseJob(jobKey);}/*** 根据任务名称恢复定时任务** param scheduler 调度器* param scheduleLog 定时任务名称这里直接用ReportSubscribePO的Id* throws SchedulerException*/public void resumeScheduleJob(Scheduler scheduler, ScheduleLog scheduleLog) throws SchedulerException {// 判断当前任务是否在调度中SetJobKey jobKeys scheduler.getJobKeys(GroupMatcher.groupEquals(jobGroup));ListJobKey thisNameJobs jobKeys.stream().filter(jobKey - Objects.equals(scheduleLog.getId().toString(), jobKey.getName())).collect(Collectors.toList());if (thisNameJobs.size() 0){JobKey jobKey JobKey.jobKey(scheduleLog.getId().toString(), jobGroup);scheduler.resumeJob(jobKey);// 下一次执行时间设置回去Trigger trigger scheduler.getTrigger(TriggerKey.triggerKey(scheduleLog.getId().toString(), jobGroup));long nextTime trigger.getNextFireTime().getTime();scheduleLog.setNextTime(nextTime);}else {createScheduleJob(scheduler, scheduleLog);}}/*** 更新定时任务** param scheduler 调度器* param scheduleLog 报告订阅对象* throws SchedulerException*/public void updateScheduleJob(Scheduler scheduler, ScheduleLog scheduleLog) throws SchedulerException {//获取到对应任务的触发器TriggerKey triggerKey TriggerKey.triggerKey(scheduleLog.getId().toString(),jobGroup);//设置定时任务执行方式CronScheduleBuilder scheduleBuilder CronScheduleBuilder.cronSchedule(scheduleLog.getCronExpression());//重新构建任务的触发器triggerCronTrigger trigger (CronTrigger) scheduler.getTrigger(triggerKey);if (trigger null){return;}// trigger trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();trigger TriggerBuilder.newTrigger().startNow().withIdentity(scheduleLog.getId().toString(), jobGroup).withSchedule(scheduleBuilder).build();//重置对应的jobscheduler.rescheduleJob(triggerKey, trigger);scheduleLog.setNextTime(trigger.getNextFireTime().getTime());}/*** 根据定时任务名称从调度器当中删除定时任务** param scheduler 调度器* param jobName 定时任务名称 这里直接用 ScheduleLog 的Id* throws SchedulerException*/public void deleteScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {JobKey jobKey JobKey.jobKey(jobName,jobGroup);scheduler.deleteJob(jobKey);}
}6. 项目启动时做任务初始化
这里我直接让service实现了CommandLineRunner然后在run()方法中将初始化逻辑写入进来让数据库中的持久化的任务全部添加进内存中。
Service
public class ScheduleLogServiceImpl implements ScheduleLogService, CommandLineRunnerprivate final static Logger logger LoggerFactory.getLogger(ScheduleLogServiceImpl.class);Autowiredprivate ScheduleLogDao scheduleLogDao;Autowiredprivate ScheduleQuartzManage scheduleQuartzManage;Autowiredprivate Scheduler scheduler; //这里的scheduler来源于配置Overridepublic void run(String... args) throws Exception {// 初始化所有的已经启用的订阅ListScheduleLog enableSubs getEnableScheduleList();logger.info(需要初始化的任务个数:{}, enableSubs.size());for (ScheduleLog sub : enableSubs) {try {logger.info(开始初始化订阅任务,任务name:{}, sub.getName());scheduleQuartzManage.createScheduleJob(scheduler, sub);} catch (Exception e) {logger.error(启动时初始化订阅任务失败:{}, e.getMessage());}}}Overridepublic ListScheduleLog getEnableScheduleList() {HashMapString, Object param new HashMap();param.put(status,1);ListScheduleLog list scheduleLogDao.getList(param);return list;}Overridepublic boolean add(Map data) {data.put(createTime, DateUtil.currentSeconds());Integer add scheduleLogDao.add(data);if(add 0){//创建调度器data.put(id,add);ScheduleLog scheduleLog BeanUtil.mapToBean(data, ScheduleLog.class,false,null);if(scheduleLog.getStatus() 1){return true;}try {scheduleQuartzManage.createScheduleJob(scheduler, scheduleLog);} catch (SchedulerException e) {e.printStackTrace();}return true;}return false;}Overridepublic boolean del(Integer id) {Integer del scheduleLogDao.del(id);if(del 0){try {scheduleQuartzManage.deleteScheduleJob(scheduler,String.valueOf(id));} catch (SchedulerException e) {e.printStackTrace();}return true;}return false;}Overridepublic boolean edit(Map data) {data.put(updateTime,DateUtil.currentSeconds());Integer save scheduleLogDao.save(data);if(save 0){try {ScheduleLog scheduleLog getDetail((Integer) data.get(id));if(data.get(status).equals(0)){//关闭scheduleQuartzManage.deleteScheduleJob(scheduler,data.get(id).toString());}else{//开启scheduleQuartzManage.deleteScheduleJob(scheduler,data.get(id).toString());scheduleQuartzManage.createScheduleJob(scheduler,scheduleLog);}} catch (SchedulerException e) {e.printStackTrace();}return true;}return false;}
}至此基本就实现了定时任务的管理了controller 里的内容包含了对定时任务进行管理的接口就不写了
总结
同一个任务是否可以并行执行可参考第3步设置每次项目重新部署后自动加载数据库中的定时任务Quartz在发生异常时会重试一次注意异常处理可在第3步中处理
参考链接https://juejin.cn/post/7054762566035193869#heading-7
Cron表达式
不管是Spring自带的定时任务实现还是SpringBoot整合Quartz的定时任务实现其触发器都支持用corn表达式来表示。
corn表达式是一个字符串有6或7个域域之间是用空格进行间隔。
从左到右每个域表示的含义如下
第几个域英文释义允许值备注一Seconds0~59秒二Minutes0~59分三Hours0~23时四DayOfMonth1-31天五Month1-12或月份简写月六DayOfWeek1-7或星期简写星期1表示SUN在day-of-week字段用”6#3”指这个月第3个周五6指周五3指第3个。如果指定的日期不存在触发器就不会触发七Year1970~2099年
然后某些域还支持部分特殊字符特殊字符的含义如下
特殊字符含义及注意事项*任意值?占位符只能在第四域和第六域中使用表示未说明的值即不关心它为何值-区间表示区间内有效/固定间隔符号前表示开始时间符号后表示每次递增的值,枚举有效值的间隔符表示附加一个可能值L表示该区间的最后一个有效值只能在第四域和第六域中使用 L(“last”) (“last”) “L” 用在day-of-month字段意思是 “这个月最后一天”用在 day-of-week字段, 它简单意思是 “7” or “SAT”。 如果在day-of-week字段里和数字联合使用它的意思就是 “这个月的最后一个星期几” – 例如 “6L” means “这个月的最后一个星期五”. 当我们用“L”时不指明一个列表值或者范围是很重要的不然的话我们会得到一些意想不到的结果。W表示离指定日期的最近的有效工作日周一-周五为工作日W(“weekday”) 只能用在day-of-month字段。用来描叙最接近指定天的工作日周一到周五。例如在day-of-month字段用“15W”指“最接近这个月第15天的工作日”即如果这个月第15天是周六那么触发器将会在这个月第14天即周五触发如果这个月第15天是周日那么触发器将会在这个月第16天即周一触发如果这个月第15天是周二那么就在触发器这天触发注意一点这个用法只会在当前月计算值不会越过当前月。“W”字符仅能在day-of-month指明一天不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日。
常用corn表达式例子含义说明
corn表示式表达式含义*/10 * * * * ? *每隔10秒执行一次0 30 1 * * ? *每天凌晨1点30分0秒开始执行0 0 10,14,16 * * ?每天10点、14点、16点执行一次0 15 10 L * ?每个月最后一天的10点15分执行一次0 15 10 ? * 6L每月的最后一个星期五上午10:15触发0 15 10 15 * ?每月15日上午10:15触发0 15 10 ? * 6#3每月的第三个星期五上午10:15触发0 15 10 ? * 6L 2018-20202018年到2020年每个月最后一个星期五的10:15执行
校验地址https://www.bejson.com/othertools/cronvalidate/