一、任务调度概述
各种企业应用几乎都会碰到任务调度的需求。比如:对于一个典型的企业应用系统来说,在每月 1 号凌晨统计上个月各部门的业务数据并生成月报表;每半个小时查询用户是否有快到期的待处理的紧急业务等。
任务调度除以时间为关注点外,还应考虑资源的分配。大多数系统都要对资源使用进行控制,比如:
服务线程的最大数目必须限额
考虑使用线程池以便共享服务的线程资源、降低频繁创建、销毁线程带来的系统资源消耗
1、Quartz概述
OpenSymphony 所提供的 Quartz 自 2001 年发布版本以来已被众多项目作为任务调度的解决方案,Quartz 在提供灵活性的同时并未牺牲其简单性,它所提供的强大功能使开发者可以轻松应对绝大多数任务调度的功能需求。
Quartz 提供了强大的任务调度机制。Quartz 允许开发人员灵活地定义触发器的调度时间表,并可对触发器和任务进行关联映射。
2、Quartz的基础结构
Quartz 对任务调度领域的问题进行了高度抽象,提出了调度器、任务和触发器 3 个核心概念,并在 org.quartz 包中通过接口和类对核心概念进行了描述。
1️⃣Job:是一个接口,开发者通过实现该接口定义需要执行的任务
2️⃣JobDetail:也是一个接口,Quartz 在每次执行 Job 时,都重新创建一个 Job 的实例,所以它并不是直接接收一个 Job 的实例,而是接收一个 Job 的实现类,以便运行时通过反射机制实例化 Job。因此需要一个类来描述 Job 的实现类及其他相关的静态信息,如 Job 名称、描述、关联的监听器等,JobDetail 承担了这一角色
3️⃣Trigger:描述触发 Job 执行的规则。主要有 SimpleTrigger、CronTrigger 两个实现类
SimpleTrigger:当仅需要触发一次或者以固定间隔周期执行时使用
CronTrigger:可以通过 Cron 表达式定义出各种复杂的调度方案
4️⃣Calendar:一些日历特定时间点的集合(可以简单的理解为 java.util.Calendar 的集合)。一个 Trigger 可以和多个 Calendar 关联,以便包含或排除某些时间点
5️⃣Scheduler:一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组及名称。组及名称是 Scheduler 查找容器中某一对象的依据,Trigger 和 JobDetail 的组及名称的组合都必须唯一(但 Scheduler 和 Trigger 的组合名称可以相同,因为他们是不同的类型,处于不同的容器中)。Scheduler 可以将 Trigger 绑定到某一个 JobDetails 中,这样当 Trigger 被触发时,对应的 Job 就被执行。一个 Job 可以对应多个 Trigger,但一个 Trigger 只能对应一个 Job
二、SimpleTrigger使用案例
1、步骤概览
①实现 Job 接口,使 Java 类变为可调度的任务
②创建描述 Job 的 JobDetail 对象
③创建 SimpleTrigger 对象
④设置触发 Job 执行的时间规则
⑤通过 SchedulerFactory 获取 Scheduler 对象
⑥向 SchedulerFactory 中注册 JobDetail 和 Trigger
⑦启动调度任务
2、案例详情
①导包
quartz-2.2.1.jar
quartz-jobs-2.2.1.jar
slf4j-api-1.6.1.jar
②创建Job的实现类:在execute方法中编写要执行的任务逻辑,后续调度的时候就是执行此处的代码
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("执行调度:" + new Date());
}
}
③定时调度
public class QuartzTest {
public static void main(String[] args) throws SchedulerException{
//创建JobDetail对象用以描述Job:name、group、jobClass必须指定
JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setName("Test-Quartz");
jobDetail.setGroup("Test-Group");
jobDetail.setJobClass(MyJob.class);
//创建simpleTrigger对象:必须指定name
SimpleTriggerImpl simpleTrigger = new SimpleTriggerImpl();
simpleTrigger.setName("Test-Trigger");
//指定Trigger的触发规则
simpleTrigger.setStartTime(new Date());//开始触发的时间
simpleTrigger.setRepeatInterval(2000);//每2秒钟触发一次
simpleTrigger.setRepeatCount(10);//共重复10次
//通过SchedulerFactory获取Scheduler对象
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//在scheduler中注册jobDetail和trigger,然后开启任务调度即可
scheduler.scheduleJob(jobDetail, simpleTrigger);
scheduler.start();
}
}
三、CronTrigger使用案例
CronTriggr 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(例如:每月第一个周一执行),而不是简单的周期时间间隔。相对于SimpleTrigger更常使用。
1、Cron表达式
Quartz 使用类似于 Linux 下的 Cron 表达式定义时间规则,Cron 表达式由 6 或 7 个空格分隔的时间字段组成,Cron 表达式对特殊字符的大小写不敏感。
*:可用于所有字段,表示对应时间域的每一个时刻。例如:* 在分钟字段时,表示每分钟
?:只能用在日期和星期字段中,表示一个毫无意义的值,相当于占位符
-:表示一个范围。例如:在小时字段中使用 10-12,表示从 10 点到 12 点。即 10点、11点、12点
,:表示一个列表值。例如:在星期字段中使用 MON,WED,FRI,表示星期一、星期三、星期五
/:x/y 表示一个等步长序列,x 为起始值,y 为增量步长。例如:在分钟字段中使用 0/15,则表示为 0,15,30 和 45 秒;而 5/15 在分钟字段表示 5,20,35,50 。*/y 等同于 0/y
L:该字符只在日期和星期字段中使用,代表 Last。L 用在日期字段中,代表这个月的最后一天;L 用在星期字段中,代表星期六,而不是星期天;若 L 出现在星期字段里,且前面有一个数值 X,则表示这个月最后的星期 X,例如 6L 表示该月的最后一个星期五,注意和西方相差一天
W:该字符只能出现在日期字段里,是对日期的修饰,表示离该日期最近的工作日。例如:15W 表示离该月 15 号最近的工作日,若 15 号是星期六,则匹配 14 号;若 15 号是星期日,则匹配 16 号;若 15 号是星期二,则匹配 15 号。但是要注意关联的匹配日期不能跨月,例如用户指定 1W,若 1 号是星期六,则匹配 3 号而不是上个月的最后一天。另外W 字符串只能指定单一日期,而不能指定日期范围
LW:在日期字段可以组合使用 LW,意为当月的最后一个工作日
#:只能在星期字段中使用,表示当月第几个工作日。例如:6#3 表示当月的第三个星期五(6表示星期五,#3表示当月的第三个),4#5 表示当月的第五个星期三,若没有第五个星期三,则忽略不触发
C:该字符只在日期和星期字段中使用,代表 Calendar。意为计划所关联的日期,如果日期没有被关联,则相当于日历中的所有日期。例如:5C 在日期字段中就相当于 5 日以后的第一天。1C 在星期字段中相当于星期日后的第一天
2、Cron表达式示例
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触发
3、CronTrigger案例
①Job的实现类
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("执行调度:" + new Date());
}
}
②使用CronTrigger做任务调度
public class CronTriggerQuartzTest {
public static void main(String[] args) throws ParseException, SchedulerException {
JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setName("CronJob-Test");
jobDetail.setGroup("Test-Cron");
jobDetail.setJobClass(MyJob.class);
CronTriggerImpl cronTrigger = new CronTriggerImpl();
cronTrigger.setName("CronTrigger-Test");
cronTrigger.setGroup("Test-CronTrigger");
//指定触发时间的规则:表示2016年1月30日9点08分触发,每3秒钟触发一次,持续一分钟
cronTrigger.setCronExpression("0/3 8 9 30 1 ? 2016");
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
}
}
四、Quartz与Spring集成
Spring 进一步降低了使用 Quartz 的难度,这体现在Spring为创建 Quartz 的 Scheduler、Trigger 和 JobDetail 提供了便利的 FactoryBean 类,以便能够在 Spring 容器中配置和自动注入。
1、在Spring中配置JobDetail
Spring 提供了一个 MethodInvokingJobDetailFactoryBean,通过这个 FactoryBean 可以将 Spring 容器中的 Bean 的方法包装成 Quartz 任务,这样就不必为 Job 创建对应的实现类了。配置JobDetail之前必须先指定一个执行任务调度的类和执行任务调度的方法,但该 bean 不需要实现任何接口
public class MyJob {
public void doWork() throws JobExecutionException {
System.out.println("do My Job: " + new Date());
}
}
在Spring容器中注册该bean:
<bean id="quartzJob" class="com.cailutong.job.MyJob"></bean>
通过MethodInvokingJobDetailFactoryBean配置JobDetail:指定调度哪个对象的哪个方法
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref bean="quartzJob"/>
</property>
<property name="targetMethod">
<value>doWork</value>
</property>
</bean>
2、配置CronTrigger
Spring 为 CronTrigger 提供了更具 Bean 风格的 CronTriggerFactoryBean
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"/>
<property name="cronExpression" value="10/5 * * * * ?"/>
</bean>
3、配置Scheduler
Spring 提供了 SchedulerFactoryBean,这个 Bean 拥有如下功能:
1️⃣以更具有 Bean 风格的方式为 Scheduler 提供配置信息
2️⃣让 Scheduler 和 Spring IOC 容器的生命周期建立关联:IOC 容器启动后,Secheduler 开始工作,IOC 容器关闭前,自动关闭 Scheduler
3️⃣startupDelay 属性:在 SchedulerFactoryBean 初始化完成后,延迟多少秒启动 Scheduler,默认为 0,即立即启动。一般情况下,可以通过设置该属性使 Scheduler 延迟一小段时间后启动,以便让 Spring 能够更快初始化容器中的其它 Bean
4️⃣quartzPorperties属性:类型为 Properties,允许在 Spring 中定义 Quartz 的属性,其值将覆盖 quartz.properties 配置文件中的配置
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="cronTrigger1"/>
</list>
</property>
<property name="quartzProperties">
<props>
<prop key="org.quartz.threadPool.threadCount">5</prop>
</props>
</property>
<property name="startupDelay" value="2"></property>
</bean>
这样配置好之后,当项目启动,Spring容器就会初始化好这些相关的bean,定时调度也就启动了。