Springcloud学习笔记37--任务调度框架Quartz 使用01(Cron表达式)与@scheduled注解定时任务
1.Quartz简介
Quartz是一款Java编写的开源任务调度框架,同时它也是Spring默认的任务调度框架。它的作用其实类似于Java中的Timer定时器以及JUC中的ScheduledExecutorService调度线程池,当然Quartz作为一个独立的任务调度框架无疑在这方面表现的更为出色,功能更强大,能够定义更为复杂的执行规则。
基于定时、定期的策略来执行任务是它的核心功能,比如x年x月的每个星期五上午8点到9点,每隔10分钟执行1次。
Quartz中主要用到了:Builder建造者模式、Factory工厂模式以及组件模式。
Quartz有3个核心要素:任务(Job)、触发器(Trigger)、调度器(Scheduler)。
任务(Job,你需要做什么事?我们需要将具体的业务逻辑写到实现了Job
接口的实现类中)
触发器(Trigger
,你什么时候去做?它定义了任务的执行规则,什么时候开始执行,什么时候结束执行)
调度器(Scheduler
,你什么时候需要去做什么事?通过传入的任务Job
和触发器Trigger
,以指定的规则执行任务)。
Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。Quartz不仅仅是线程和线程池管理。为确保可伸缩性,Quartz采用了基于多线程的架构。启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作业。这就是Quartz能并发运行多个作业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。
2.Quartz的体系结构
JobDetail:quartz每次都会直接创建一个JobDetail,同时创建一个Job实例,它不直接接受一个Job的实例,但是它接受一个Job的实现类,通过new instance()的反射方式来实例一个Job,在这里Job是一个接口,我们需要自己编写类去实现这个接口。
Trigger : 它由SimpleTrigger和CronTrigger组成,SimpleTrigger实现类似Timer的定时调度任务,CronTrigger可以通过cron表达式实现更复杂的调度逻辑·。
SimpleTrigger很方便,如果你需要一次性执行(只是在一个给定时刻执行job),或者如果你需要一个job在一个给定的时间,并让它重复N次,并在执行之间延迟T。
CronTrigger是有用的,如果你想拥有引发基于当前日历时间表,如每个星期五,中午或在每个月的第十天 10:15。
Scheduler:调度器,JobDetail和Trigger可以通过Scheduler绑定到一起。任务调度器,是实际执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。
3. Quartz重要组成部分
3.1 Job接口
Job是一个接口,只有一个方法void execute(JobExecutionContext context) , 这是我们自己的具体业务逻辑的入口。作业类需要实现接口中的execute方法,JobExecutionContext提供了调度的上下文信息,每一次执行Job都会重新创建一个Job对象实例。
要创建一个任务,我们需要编写一个实现该接口的具体任务类:
public class HelloJob implements Job{ public void execute(JobExecutionContext context) throws JobExecutionException { //编写我们自己的业务逻辑 } }
3.2 JobDetail
JobDetail
描述了Job
对象的基本信息,主要包含四个重要的属性:name
(Job的名称)、group
(Job的组名称)、jobClass
(Job对应的类)以及jobDataMap
(存储一些用户自定义的信息或对象)。在Scheduler
中Job
的名称name
和组group
组合必须是唯一的。
quartz每次都会直接创建一个JobDetail,同时创建一个Job实例,它不直接接受一个Job的实例,但是它接受一个Job的实现类,通过new instance()的反射方式来实例一个Job.可以通过下面的方式将一个Job实现类绑定到JobDetail中
// 指明job的名称,所在组的名称,以及绑定job类 JobDetail job = JobBuilder.newJob(HelloJob.class) //绑定job类 .withIdentity("JobName", "JobGroupName") //指明job的名称为JobName,所在组的名称为JobGroupName .usingJobData(jobDataMap) //传递job执行时需要的数据 .build();
3.3 JobBuiler
主要是用来创建jobDeatil实例
3.4 JobDataMap数据存储类
通过查看JobDetail
、Trigger
及JobExecutionContext
的源码可以发现,他们中都存在JobDataMap
这个类型,它是以Map
的形式存储我们的一些自定义数据的。当Job
对象的execute
方法被调用时,JobDataMap
会通过JobExecutionContext
传递给execute
方法,它可以用来装载任何可序列化的数据对象。JobDataMap
实现了Java中的Map
接口,提供了一些自己的方法来存储数据。
这是JobDataMap
的继承树:
可以看到JobDataMap
是DirtyFlagMap
的子类,而DirtyFlagMap
实际实现了Java中的java.util.Map
类型:
// DirtyFlagMap是java.util.Map接口的子类 public class DirtyFlagMap<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { }
一句话:把它当Java中的map
来用就对了!
3.5 trigger
前文讲到它主要用来执行Job实现类的业务逻辑的,我们可以通过下面的代码来创建一个Trigger实例
CronTrigger trigger = (CronTrigger) TriggerBuilder .newTrigger() .withIdentity("myTrigger", "group1") //创建一个标识符 .startAt(date)//什么时候开始触发 //每秒钟触发一次任务 .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")) .build();
3.6 Scheduler
创建Scheduler有两种方式
通过StdSchedulerFactory来创建
SchedulerFactory sfact=new StdSchedulerFactory(); Scheduler scheduler=sfact.getScheduler();
通过DirectSchedulerFactory来创建
DiredtSchedulerFactory factory=DirectSchedulerFactory.getInstance();
Scheduler scheduler=factory.getScheduler();
Scheduler 配置参数一般存储在quartz.properties中,我们可以修改参数来配置相应的参数。通过调用getScheduler()方法就能创建和初始化调度对象。
Scheduler的主要函数介绍:
Date schedulerJob(JobDetail,Trigger trigger);返回最近触发的一次时间 void standby()暂时挂起 void shutdown()完全关闭,不能重新启动了 shutdown(true)表示等待所有正在执行的job执行完毕之后,再关闭scheduler shutdown(false)即直接关闭scheduler
在这里我们不得不提一下quartz.properties这个资源文件,在org.quartz这个包下,当我们程序启动的时候,它首先会到我们的根目录下查看是否配置了该资源文件,如果没有就会到该包下读取相应信息,当我们咋实现更复杂的逻辑时,需要自己指定参数的时候,可以自己配置参数来实现。下面我们简单看一下这个资源文件:
org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
该资源文件主要组成部分:
①调度器属性
②线程池属性
③作业存储设置
④插件设置
<1>调度器属性:
org.quartz.scheduler.instanceName属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名。
org.quartz.scheduler.instanceId属性和前者一样,也允许任何字符串,但这个值必须是在所有调度器实例中是唯一的,尤其是在一个集群当中,作为集群的唯一key,假如你想quartz帮你生成这个值的话,可以设置我Auto
<2>线程池属性:
threadCount设置线程的数量
threadPriority设置线程的优先级
org.quartz.threadPool.class 线程池的实现
<3>作业存储设置:
描述了在调度器实例的声明周期中,job和trigger信息是怎么样存储的
<4>插件配置:
满足特定需求用到的quartz插件的配置
4. Cron表达式
在这里,我们着重讲解一下cron表达式,quartz之所以能够实现更加复杂的业务逻辑,主要在依赖于cron表达式。
cron表达式编写的顺序依次是”秒 分 时 日 月 周 年”。
在线Cron生成表达式:http://cron.qqe2.com/
cron 一定有七位数,最后一位是年,SpringBoot 定时方案只需要设置六位即可:
- 第一位, 表示秒, 取值是0 ~ 59,允许的字符为,- * /
- 第二位, 表示分. 取值是0 ~ 59,允许的字符为 ,- * /
- 第三位, 表示小时, 取值是0 ~ 23,允许的字符为,- * /
- 第四位, 表示天/日, 取值是0 ~ 31,允许的字符为,- * ? / L W C
- 第五位, 表示月份, 取值是1 ~ 12,允许的字符为 ,- * /
- 第六位, 表示星期, 取值是1 ~ 7, 星期一,星期二…, 还有 1 表示星期日,允许的字符为,- * ? / L W C
- 第七位, 年份, 可以留空, 取值是1970 ~ 2099
cron中,还有一些特殊的符号,含义如下:
- (*) 星号,可以理解为每的意思,每秒、每分、每天、每月、每年…。
- (?) 问号,问号只能出现在日期和星期这两个位置,表示这个位置的值不确定,每天 3 点执行,因此第六位星期的位置,是不需要关注的,就是不确定的值;同时,日期和星期是两个相互排斥的元素,通过问号来表明不指定值,比如 1 月 10 日是星期一,如果在星期的位置另指定星期二,就前后冲突矛盾了。
- (-) 减号,表达一个范围,如在小时字段中使用“10 - 12”,则表示从 10 到 12 点,即 10、11、12。
- (,) 逗号,表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一、星期二、星期四。
- (/) 斜杠,如 x/y,x 是开始值,y 是步长,比如在第一位(秒),0/15 就是从 0 秒开始,每隔 15 秒执行一次,最后就是 0、15、30、45、60,另 */y,等同于 0/y。
举几个例子熟悉一下:
表达式 意义 "0 0 12 * * ?" 每天中午12点触发 "0 15 10 ? * *" 每天上午10:15触发 "0 15 10 * * ?" 每天上午10:15触发 "0 15 10 * * ? *" 每天上午10:15触发 "0 15 10 * * ? 2005" 2005年的每天上午10:15触发 "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 * ?" 每月15日上午10:15触发 "0 15 10 L * ?" 每月最后一日的上午10:15触发 "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发 特殊字符 意义 * 表示所有值; ? 表示未说明的值,即不关心它为何值; - 表示一个指定的范围; , 表示附加一个可能值; / 符号前表示开始时间,符号后表示每次递增的值; L("last") ("last") "L" 用在day-of-month字段意思是 "这个月最后一天";用在 day-of-week字段, 它简单意思是 "7" or "SAT"。 如果在day-of-week字段里和数字联合使用,它的意思就是 "这个月的最后一个星期几" – 例如: "6L" means "这个月的最后一个星期五". 当我们用“L”时,不指明一个列表值或者范围是很重要的,不然的话,我们会得到一些意想不到的结果。 W("weekday") 只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在day-of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日。 # 只能用在day-of-week字段。用来指定这个月的第几个周几。例:在day-of-week字段用"6#3"指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。 C 指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天
5.Quartz框架实战
5.1 maven依赖
添加quartz的依赖:
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
核心类:
Scheduler :调度器,所有Job的调度都是由它控制;
JobDetail :生成Job对象的实例,存储Job对象需要的参数;
Job :执行业务逻辑;
Trigger :定义触发的条件;
帮助类
SimpleScheduleBuilder:用于构建Scheduler:
JobBuilder :用于构建JobDetail:
TriggerBuilder :用于构建Trigger;
5.2 创建job
首先创建一个Quartz任务,任务中从JobExecutionContext
中获取到了JobDetail
和Trigger
中的JobDataMap
,并从中取到了客户端QuartzScheduler
中传入的数据:
/** * @Author lucky * @Date 2021/12/27 9:18 */ public class HelloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobDetail detail = jobExecutionContext.getJobDetail(); String name = detail.getJobDataMap().getString("name"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //显示的格式 String date = sdf.format(new Date()); System.out.println(date+": say hello " + name ); } }
5.3 任务调度测试案例
创建Quartz客户端,构建JobDetail
和Trigger
并使用Scheduler
开始任务调度(这里要注意的是Scheduler
实例创建后处于“待机”状态,所以别忘了调用start
方法启动调度器,否则任务是不会执行的!):
/** * @Author lucky * @Date 2021/12/27 9:23 */ public class JobTest { public static void main(String[] args) throws InterruptedException { // 创建工厂 SchedulerFactory schedulerfactory = new StdSchedulerFactory(); Scheduler scheduler = null; try { // 通过schedulerFactory获取一个调度器 scheduler = schedulerfactory.getScheduler(); JobDataMap jobDataMap=new JobDataMap(); jobDataMap.put("name","quartz" ); // 创建一个JobDetail实例,指明job的名称,所在组的名称,以及绑定job类 JobDetail job = JobBuilder.newJob(HelloJob.class) //绑定job类 .withIdentity("JobName", "JobGroupName") //指定JobDetail的名称和组名称 .usingJobData(jobDataMap) //使用jobDataMap存储用户数据, jobDataMap为JobDetail传递的文本数据 .build(); // 构建一个Trigger(定义触发的条件),指定Trigger名称和组,规定该Job立即执行,且3秒钟重复执行一次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("CronTrigger1", "CronTriggerGroup") //指定Trigger名称和组 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()) // 设置运行规则,每隔3秒执行一次,一直重复下去 .startNow() // 执行的时机,立即执行 .build(); //绑定JobDetail和Trigger scheduler.scheduleJob(job, trigger); //开始任务调度 scheduler.start(); Thread.sleep(30000); // 停止任务调度 scheduler.shutdown(); } catch (SchedulerException e) { e.printStackTrace(); } } }
控制台输出:
6. @scheduled注解定时任务
在 Spring Boot 中使用 @Scheduled 注解创建定时任务非常简单,只需要两步操作就可以创建一个定时任务:
(1)在定时任务类上增加 @EnableScheduling 注解
(2)在要执行任务的方法上增加 @Scheduled 注解
(3)ShedLock的作用,确保任务在同一时刻最多执行一次。如果一个任务正在一个节点上执行,则它将获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只需跳过它即可
package com.ttbank.flep.core.job; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; /** * @Author lucky * @Date 2022/1/27 10:37 */ @Component @Configurable @EnableScheduling public class ScheduledTasks { /** * 每6秒执行一次 **/ @Scheduled(cron = "*/6 * * * * * ") public void reportCurrentByCron(){ SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + sdf.format (new Date())); } }
控制台输出:
参考文献:
https://zhuanlan.zhihu.com/p/133208221---非常好
https://zhuanlan.zhihu.com/p/133211946
https://blog.csdn.net/chengqiuming/article/details/84187419---非常好
https://blog.csdn.net/cyan20115/article/details/106550915
https://blog.csdn.net/java_hanyu_tel/article/details/79697161----非常好
https://www.cnblogs.com/haw2106/p/9950826.html-----非常好
https://www.cnblogs.com/loong-hon/p/10912741.html-----------非常好。
https://www.cnblogs.com/niceyoo/p/10917461.html
https://blog.csdn.net/qq_34279574/article/details/120776854---注解定时任务框架
https://blog.csdn.net/wangmx1993328/article/details/105057405 非常推荐
https://www.cnblogs.com/muxi0407/p/11969119.html 推荐
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)