SpringBoot Quartz 定时任务
简介
Quartz是一个完全由Java编写的开源作业调度框架。项目由于需要使用定时任务,考虑到其与Spring里面的SpringTask在cron表达式的一个区别,所以选择了Quartz(具体什么区别末尾会提及)。不得不说,Quartz在定时任务这块操作起来简易得让人发指。起一个定时任务超级简单。
- 创建实现org.quartz.Job接口的java类,其中只有一个方法:execute (JobExecutionContext context) throws JobExecutionException,将要实现的任务逻辑添加进execute这个方法里
- 创建JobDetail
- 创建触发器(CronTrigger)
- Scheduler开启定时任务
名词阐述
Job
是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
JobDetail
Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
Trigger
是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;
Scheduler
代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
项目需求
近期做的一个项目中,有一个这样的需求,活动报名通道需要根据预设好的截止时间结束活动报名,截止时间可以为空,时间格式是yyyy-MM-dd HH:mm:ss
配置
加入Quartz,需要以下两个依赖:1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
应用场景
在将quartz应用到项目之前,还了解到quartz可应用于单任务以及多任务
单任务
虽然单任务不是项目需求所要求的解决方案,还是写一下,以后需要的时候可以翻出来看看,而单任务在我解决项目需求前,曾动手实验了一番,正是因为跑过代码,才发现单任务不太适合这个项目需求
单任务即整个项目就只有一个定时任务,与用户数量无关,比如定时清理数据库日志,定时全站通告等等
任务
1 | @Configuration |
配置
1 | @Configuration |
上述设置时,会在项目启动的5秒之后,每隔6秒执行一次啊ScheduleTask中的scheduleTest方法。若想动态改变cron表达式,可以重新生成一个trigger,schedule重新规划这个触发器
动态改变cron表达式
1 | @ResponseBody |
多任务
上述的单任务在项目启动时就会启动,但是在项目往往的场景是项目启动时,不要你启动,而只需要用户指定启动的时候,启动某一特定的任务。因此这一动态多任务相比单任务要简单得多
任务
实际情况是,有多少个任务就有多少个类实现Job这个接口
上面图片,实现了通过传一个id过来,去数据查找,然后更新。我的处理使将id作为Job的name,然后传过来,再将其转换为Integer类型。若遇到更多的参数的话,应通过使用JobDetail的JobDataMap传输过来,例如
1 | JobDetail job = JobBuilder.newJob(。。。).withIdentity(..., ...).build(); |
这种方式传入参数是Quartz比较推荐的。然后再execute方法里再通过JobDataMap取出来,例如
1 | JobDataMap map = context.getJobDetail().getJobDataMap() |
配置
上图步骤如下:
- 先使用JobBuilder将任务放进去,同时设置name,group,得到一个JobDetail
- 然后通过CronScheduleBuilder设置触发日期
- 接着使用TriggerBuilder获取一个触发器
- Scheduler去规划这个Job和这个触发器什么时候触发
基本步骤如上,当然项目还使用了其他的方法,如下:
综合上图可以看到,要找到一个任务,只需传入name和group,得到一个JobKey,再通过JobKey获取这个JobDetail。Quartz就是这么简单操作
Cron表达式
既然提及到定时任务,就不得不提一下Cron表达式,详情请参考这篇文章,参考一下大神的博客,受益匪浅
Cron表达式是一个字符串,以5或者6个空格隔开,分成6或者7个域,每一个域代表一个含义
结构
Cron从左到右:秒 分 时 日 月 星期 年份
各字段含义
- 秒:0~59的整数,允许,- * /四个字符
- 分:0~59的整数,允许,- * /四个字符
- 时:0~23的整数,允许, - * /四个字符
- 日:1~31的整数,允许,- * / L W C ?八个字符
- 月:1~12的整数,或者JAN-DEC,允许, - * /四个字符
- 星期:1~7的整数,或者SUN-SAT,允许,- * / L C # ? 八个字符
- 年:1970~2099,允许,- * /四个字符
注意:
*:表示匹配任意值
?:只能用在日和星期这两个地方,
-:表示范围,如分钟可用5-20 表示5到20分钟
/:表示起始时间开始触发,然后每隔固定时间触发一次,如5/20:可表示5分钟触发一次,而下一次触发为25分钟,再下一次为45分钟
,:表示列出枚举值
L:表示最后,只能出现在日和星期这两个地方,例如5L表示最后一个周四触发
W:表示有效工作日(周一到周五),只能出现在日,系统将在离指定日期的最近有效工作日触发,比如5W,如果5号是周六,将在最近的周五触发,如果5号是在周日,将在最近的周一触发。#:表示用于确定每个月第几个星期几,比如:4#2 表示某月的第2个星期三
Quartz和SpringTask区别
- 第一个也是最重要的一个,就是精确度:
Quarz的精确度可以精确到某年某月某日某时某分某秒,而SpringTask或者别的Timer不行,看了下SpringTask源码,只能够让我们精确到某月某日某时某分某秒以及星期,相比于Quartz差了很多,这也是我一开始尝试SpringTask后,遇到这个坑,再转去Quartz的。
- 对异常的处理:
Quartz在某次执行任务过程中抛出异常,不影响下一次任务执行。
SpringTask一旦某个任务执行过程出现异常,整个任务周期结束,不再执行
- 任务执行过程
Quartz采用多线程,而Task默认采用单线程串行执行,若任务时间长,后续任务无法展开。
- 部署
这个区别还没遇到过,Quartz采用集群方式,分布式部署到多台机器,分配执行定时任务。
总结
在项目中,用上了Quartz以及用之前踩过的坑,能够深入理解到定时任务调度这块还是Quartz做的比较好。在理解过程中,应该寻着源码去探究。如果一个异常跑出来了,这就是学习机会,这可以通过打印出来的堆栈信息一步一步去看里面设置了什么东西。尽量避免面向百度编程吧233333333333