Spring4学习:任务调度和异步执行器之Quartz框架

时间:2020-11-29 20:03:21

一、Quartz基础知识

1、Quartz基础结构

(1)Job:接口,只有一个方法void execute(JobExecutionContext context),通过实现该接口来定义需要执行的任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。

(2)JobDetail:Quartz在每次支持Job时,都重新创建一个Job实例,所以它不是直接接收一个Job实例,而是接受一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。通过JobDetail来描述Job的实现类及其他相关的静态信息,如Job名称、描述、关联监听器等信息。

(3)Trigger:描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定间隔周期性执行时,选择SimpleTrigger,而CronTrigger则可以通过Cron表达式定义出各种复杂的调度方法。

(4)Calendar:一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。Quartz提供了若干个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义。

(5)Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,二者在Scheduler中拥有各自的组及名称。组及名称是Scheduler查找定位容器中某一对象的依据。Scheduler可以将Trigger绑定到某一JobDetail中,这样,当Trigger被触发时,对应的Job就被执行。

(6)ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程来提高运行效率。

2、使用SimpleTrigger

SimpleTrigger(String name,String group):通过构造函数指定Trigger所属组和名称。

SimpleTrigger(String name,String group,Date startTime):指定Trigger所属组、名称和触发的时间。

SimpleTrigger(String name,String group,Date startTime,Date endTime,int repeatCount,long repeatInterval):指定所属组、名称、开始时间、结束时间、重复执行次数、时间间隔。

SimpleTrigger(String name,String group,Date startTime,String jobName,String jobGroup,Date endTime,int repeatCount,long repeatInterval)在指定触发参数的同时,通过jobGroup和jobName,使该Trigger和Scheduler中的某个任务关联起来。

public class SimpleJob implements Job {
public void execute(JobExecutionContext jobCtx)
throws JobExecutionException {
System.out.println(jobCtx.getTrigger().getName()
+ " triggered. time is:" + (new Date()));
}
}
public class SimpleTriggerRunner {public static void main(String args[]) {try {JobDetail jobDetail = new JobDetail("job1_1", "jgroup1",SimpleJob.class);SimpleTrigger simpleTrigger = new SimpleTrigger("trigger1_1","tgroup1");simpleTrigger.setStartTime(new Date());simpleTrigger.setRepeatInterval(2000);simpleTrigger.setRepeatCount(100);        SchedulerFactory schedulerFactory = new StdSchedulerFactory();Scheduler scheduler = schedulerFactory.getScheduler();scheduler.scheduleJob(jobDetail, simpleTrigger);scheduler.start();} catch (Exception e) {e.printStackTrace();}}}

3、使用CronTrigger

(1)Cron表达式

1>星号(*):可用在所有字段中,表示对应时间域的每一个时刻。如:*在分钟字段表示每分钟。

2>问好(?):只在日期和星期字段中使用,相当于占位符。

3>减号(-):表示一个范围。如:10-12表示10点到12点

4>逗号(,):表示一个列表值。如:MON,WED,FRI表示星期一,星期但和星期五

5>斜杠(/):x/y表示一个等步长序列,x为起始值,y为增量步长值。如:5/15表示5,20,35,50。

6>L:只在日期和星期字段使用,代表Last。在日期字段中,表示这个月份的最后一天。在星期字段中,而且前面有一个数字N,则表示“这个月的最后N天”。例如:6L表示该月的最后一个星期五。

7>W:只出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15日最近的工作日。

8>LW组合:当月的最后一个工作日。

9>井号(#):只在星期字段中使用,表示当月的某个工作日。6#3表示当月的第3个星期五。

10>C:只在日期和星期字段使用,计划所关联的日期。5C在日期中表示5天后的那一天,1C在星期字段中表示星期日后的第一天。

(2)CronTrigger实例

public class CronTriggerRunner {
public static void main(String args[]) {
try {
JobDetail jobDetail = new JobDetail("job1_2", "jgroup1",
SimpleJob.class);
CronTrigger cronTrigger = new CronTrigger("trigger1_2", "tgroup1");

CronExpression cexp = new CronExpression("0/5 * * * * ?");
cronTrigger.setCronExpression(cexp);


SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

4、使用Calendar

使用Calendar把某天排除在执行程序的时间之外:

public class CalendarExample {

public static void main(String[] args) throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();

AnnualCalendar holidays = new AnnualCalendar();
//五一劳动节
Calendar laborDay = new GregorianCalendar();
laborDay.add(Calendar.MONTH,5);
laborDay.add(Calendar.DATE,1);
holidays.setDayExcluded(laborDay, true);
//国庆节
Calendar nationalDay = new GregorianCalendar();
nationalDay.add(Calendar.MONTH,10);
nationalDay.add(Calendar.DATE,1);
holidays.setDayExcluded(nationalDay, true);


scheduler.addCalendar("holidays", holidays, false, false);

//从5月1号10am开始
Date runDate = TriggerUtils.getDateOf(0,0, 10, 1, 5);
JobDetail job = new JobDetail("job1", "group1", SimpleJob.class);
SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1",
runDate,
null,
SimpleTrigger.REPEAT_INDEFINITELY,
60L * 60L * 1000L);
//让Trigger遵守节日的规则(排除节日)
trigger.setCalendarName("holidays");
scheduler.scheduleJob(job, trigger);
scheduler.start();
try {
// wait 30 seconds to show jobs
Thread.sleep(30L * 1000L);
// executing...
} catch (Exception e) {
}
scheduler.shutdown(true);
}
}

5、任务调度信息存储

当需要持久化任务调度信息,则Quartz允许用户通过调整其属性文件,将这些信息保存到数据库中。在使用数据库保存了任务调度信息后,即使系统崩溃后重新启动,任务调度信息仍将得到恢复。

(1)通过配置文件调整任务调度信息的保存策略

Quartz JAR文件的org.quartz包下包含了一个quartz。properties属性配置文件,并提供了默认设置。Quartz的属性文件主要包括:集群信息、调度器线程池、任务调度现场数据的保存。

#配置调度器的线程池
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.class = org.quartz.simpl.RAMJobStore

默认情况下Quartz采用org.quartz.simpl.RAMJobStore保存任务的现场数据,就是保存在RAM内存中,可以通过如下设置将任务调度线程数据保存到数据库中,

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库表前缀
org.quartz.jobStore.tablePrefix = QRTZ_
#数据源名称
org.quartz.jobStore.dataSource = qzDS
#定义数据源的具体属性
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/sampledb
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = 123456
org.quartz.dataSource.qzDS.maxConnections = 30
(2) 从数据库中恢复任务的调度  
public class JDBCJobStoreRunner {      public static void main(String args[]) {          try {              SchedulerFactory schedulerFactory = new StdSchedulerFactory();              Scheduler scheduler = schedulerFactory.getScheduler();              String[] triggerGroups = scheduler.getTriggerGroupNames();//获取调度器中所有的触发器组              for (int i = 0; i < triggerGroups.length; i++) {//重新恢复在tgroup1组中名为triggerl_1的触发器的运行                  String[] triggers = scheduler.getTriggerNames(triggerGroups[i]);                  for (int j = 0; j < triggers.length; j++) {                      Trigger tg = scheduler.getTrigger(triggers[j],triggerGroups[i]);                      if (tg instanceof SimpleTrigger && tg.getFullName().equals("tgroup1.trigger1_1")) {                          scheduler.rescheduleJob(triggers[j], triggerGroups[i],tg);                      }                  }              }              scheduler.start();          } catch (Exception e) {              e.printStackTrace();          }      }  }

二、在Spring中使用Quartz

1、创建JobDetail

(1)JobDetailFactoryBean

JobDetailFactoryBean扩展于Quartz的JobDetail。使用该Bean声明JobDetail时,Bean的名字即任务的名字,没有指定所属组,就使用默认组。除JobDetail中的属性,还定义了如下属性:

1>jobClass:类型为Class,实现Job接口的任务类

2>beanName:默认为Bean的id名,通过该属性显式指定Bean名称,它对应任务的名称。

3>jobDataAsMap:类型为Map,为任务所对应的JobDataMap提供值。

4>applicationContextJobDataKey:将ApplicationContext的引用保存到JobDataMap中,以便在Job的代码中访问ApplicationContext。但是只有指定一个键,用于在jobDataAsMap中保存ApplicationContext。如果不设置此键,JobDetailBean不会将ApplicationContext放入JobDataMap中。

5>jobListenerNames:类型为String[],指定注册在Scheduler中的JobListeners名称,以便让这些监听器对本任务的事件进行监听。

<bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"  
p:jobClass="com.smart.quartz.MyJob"
p:applicationContextJobDataKey="applicationContext">
<property name="jobDataAsMap">
<map>
<entry key="size" value="10" />
</map>
</property>
</bean>

(2)、MethodInvokingJobDetailFactoryBean

MethodInvokingJobDetailFactoryBean可以将一个Bean的某个方法封装成满足Quartz要求的Job。如下:

<bean id="jobDetail_1"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
p:targetObject-ref="myService" p:targetMethod="doJob" p:concurrent="false" />
concurrent属性指定任务类型。默认情况下封装为无状态的任务。如果需要封装为由状态的任务,需要将concurrent属性设置为false。通过MethodInvokingJobDetailFactoryBean产生的JobDetail不能被序列化,所以不能被持久化到数据库中。

2、创建Trigger

(1)SimpleTriggerFactoryBean

SimpleTriggerFactoryBean配置的Trigger名称即为Bean的名称,属于默认组。SimpleTriggerFactoryBean中的属性有:

jobDetail:对应的JobDetail

beanName:默认为Bean的id名,对应Trigger的名称。

jobDataAsMap:以Map类型为Trigger关联的JobDataMap提供值。

startDelay:延迟多少时间开始触发,单位为毫秒,默认为0。

triggerListenerNames:类型为String[],指定注册在Scheduler中的TriggerListener名称,以便让这些监听器对本触发器的事件进行监听。

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"
p:jobDetail-ref="jobDetail" p:startDelay="1000" p:repeatInterval="2000"
p:repeatCount="100">
<property name="jobDataAsMap">
<map>
<entry key="count" value="10" />
</map>
</property>
</bean>
(2)CronTriggerFactoryBean

<bean id="checkImagesTrigger" 
class="org.springframework.scheduling.quartz.CronTriggerBean"
p:jobDetail-ref="jobDetail"
p:cronExpression="0/5 * * * * ?"/>

3、创建Scheduler

SchedulerFactoryBean的配置

<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger" />
</list>
</property>
<property name="schedulerContextAsMap">
<map>
<entry key="timeout" value="30" />
</map>
</property>
<property name="quartzProperties">
<props>
<prop key="org.quartz.threadPool.class">
org.quartz.simpl.SimpleThreadPool
</prop>
<prop key="org.quartz.threadPool.threadCount">10</prop>
</props>
</property>
</bean>