基于maven与SSM框架的spring+quartz动态定时任务(一)

网络上有许多spring+quartz动态定时任务分析案例,但过于复杂,在此我总结了所有关于quartz的基本知识,并准备了一个简单的案例,实现了quartz的基本功能,而且程序中的知识点都加上了注解,针对于Java初学者
在进入动态定时之前,我们先了解一下spring框架自带的静态定时功能task

一、spring静态定时任务

1 配置文件

xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd

<!-- 定时器开关--> 
<task:annotation-driven />

2、基本应用

@Scheduled(cron= "* * * * * ?")
public void test(){
	System.out.println("执行");
}

二、动态定时任务

1、quartz中cronExpression时间表达式

(1)字段属性

字段允许值允许的特殊字符
0-59, - * /
0-59, - * /
小时0-23, - * /
日期1-31, - * ? / L W C
月份1-12 或者 JAN-DEC, - * /
星期1-7 或者 SUN-SAT, - * ? / L C #
年(可选)留空, 1970-2099, - * /

(2)特殊字符意义

特殊字符意义
*表示所有值
?表示未说明的值,即不关心它为何值;(仅用于星期和日期,选用另一个)
-表示一个指定的范围
,表示附加一个可能值
/符号前表示开始时间,符号后表示每次递增的值
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的第一天。

在MONTH和Day Of Week字段里对字母大小写不敏感。
(3)示例

表达式意义
“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触发

2、动态定时任务的基本知识

(1)概述
动态定时任务是由客户制定生成,服务端只知道执行什么任务,但任务执行时间不确定。
首先我们来回归下,常见的定时任务:

OS级别的定时任务管理器
例如linux的crontab、windows自带的计划任务。优点是:占用系统资源较少,而且操作简便,是定时任务首选的实现方式,缺点:当任务数量非常大,而且任务与任务之间有因果关系、先后顺序、竞争条件的话,OS级别的定时任务管理器就很难支持了。

编程语言自带的定时任务管理器
例如Java的timer和TimeTask。但是这些API提供的接口功能简单,往往不能满足用户定时任务设置需要,所以在项目开发过程中很少使用。

第三方组件
例如Java的quartz,python的celery等。这些组件往往既可以单独部署,也可以与当前的项目集成在一起统一部署管理,关键是他们有着强大的功能,能够满足我们对定时任务管理的各种需求,所以这些第三方组件往往在项目中应用广泛。

(2)Quartz 概述
Quartz框架是一个全功能、开源的任务调度服务
官网地址:http://www.quartz-scheduler.org
Quartz可以执行上千上万的任务调度,允许开发人员灵活的定义触发器的调度时间表,并可对触发器和任务进行关联映射。
此外Quartz提供了调度环境运行的持久化机制,可以保存并恢复调度现场,即使系统因故障关闭,调度任务现场数据也不会丢失。
Quartz还提供了组件式的侦听器、各种插件、线程池等功能

(3)Quartz 基础结构
Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器(Scheduler)、任务(Job)和触发器(Trigger)这3个核心的概念

Job是一个接口,只有一个接口方法,我们只需要实现该接口定义需要执行的任务,把任务放在execute中执行即可。JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。

JobDetail:
Quartz执行Job时,需要新建个Job实例,但是不能直接操作Job类,所以通过JobDetail来获取Job的名称、描述信息。Quartz在每次执行Job时,都重新创建一个Job实例,但是它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。
因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
重要属性如下:
name:任务的名称。
group:任务所在的组(默认值:DEFAULT)。
jobClass:任务的实现类。
jobDataMap:传参的作用。

Trigger是一个类,执行任务的规则。这个触发器实现了 Trigger 接口。描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。
当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;
CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;

Calendar:
org.quartz.Calendar和java.util.Calendar不同, 它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calend0ar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。
一个Trigger可以和多个Calendar关联, 以便排除或包含某些时间点。
针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义

Scheduler:
Scheduler是最核心的概念,代表一个Quartz的独立运行容器,Trigger和JobDetail注册到Scheduler中,并拥有各自的组及名称,组及名称是查找某一对象的依据,Trigger与JobDetail的组及名称必须唯一(但Trigger与JobDetail的组和名称可以相同)。
Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。
一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
可以通过SchedulerFactory创建一个Scheduler实例。
Scheduler拥有一个SchedulerContext,通过一个Map保存着Scheduler上下文信息,并提供了多个put()和getXxx()的方法,Job和Trigger都可访问。

ThreadPool:
Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。
无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。
有状态任务共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。所以有状态任务程序往往拥有更高的复杂度,除非必要,应该尽量使用无状态的Job。
如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
Trigger自身也可以拥有一个JobDataMap,其关联的Job可以通过JobExecutionContext#getTrigger().getJobDataMap()获取Trigger中的JobDataMap。不管有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。
Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。

注意:
不同的版本的jar包,具体的操作不太相同,但是思路是相同的;比如1.8.6jar包中,JobDetail是个类,直接通过构造方法与Job类关联。SimpleTrigger和CornTrigger是类;在2.0.2jar包中,JobDetail是个接口,SimpleTrigger和CornTrigger是接口

配置文件(本案例未关系到此知识点)
Quartz 使用的配置文件是 quartz.properties。如果使用最基本的配置,那么这个文件不是必须的,否则,这个文件必须包含在 classpath 中。
如果你创建的是 web 应用程序,那么 quartz.properties 文件需要部署到 WEB-INF/classes 中,也就是 classpath 中。

配置:
Quartz 的最大优势就是可配置。最好的配置方式是编写 quartz.properties 文件并放到项目的 classpath 中。
配置一个最基本的 quartz.properties 文件:
org.quartz.scheduler.instanceName = MyScheduler(创建的调度器命名为 MyScheduler)
org.quartz.threadPool.threadCount = 3(线程池中有 3 个线,意味着最多只能同时运行 3 个任务)
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
所有 Quartz的数据,例如任务和触发器的数据都存放到内存中(而不是数据库中)。即使你使用了数据库同时也使用了 Quartz,也建议使用 RamJobStore。

案例在下一篇文章中展现!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值