在JavaEE系统中,我们会经常用到定时任务,比如每天凌晨生成前天报表,每一小时生成汇总数据等等。
我们可以使用java.util.Timer结合java.util.TimerTask来完成这项工作,但时调度控制非常不方便,并且我们需要大量的代码。
使用Quartz框架无疑是非常好的选择,并且与Spring可以非常方便的集成。
Spring提供了支持时序调度的整合类。整个构建任务调度服务需要三步:
1)向项目中添加jar包:添加quartz.jar包,将他加到你工程的classpath中去。
2)写Class文件,在文件中定义你要执行操作的函数你就可以通过配置来达到定时操作了。
3)提供applicationContext.xml Spring配置文件,其中配置你的定时发送操作以及设置定时器的各种属性(包括运行频率和初始运行时机)。
小编做了一个每5秒打印一次当前时间的例子,具体请参考源码,尤其注意配置文件的写法。(详见下面“示例”)
【知识准备】
什么是Quartz?
Quartz是一个强大的企业级任务调度框架。它允许开发人员灵活地定义触发器的调度时间表,并可对触发器和任务进行关联映射。此外,Quartz提供了调度运行环境的持久化机制,可以保存并会发调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失。Spring中继承并简化了Quartz。
如何使用Quartz?
对于Quartz,我们使用的时候主要是注重两个方面,一个是定时任务的业务,另一个就是Cron表达式。
1>Quartz存在两种方式来定义定时执行任务,一种是使用QuartJobBean和JobDetailBean;另一种是使用MethodInvokingJobDetailFactoryBean。
2>Cron表达式包括下面7个字段并区别顺序:秒0-59,分0-59,小时0-23,月内日期1-31,月1-12或者JAN-DEC,周内日期1-7或者SUN-SAT,年(可选字段)留空或者1970-2099并且通过特殊字符表示特殊意义,具体为下:
斜线(/)字符表示增量值。例如,在秒字段中"5/15"代表从第5秒开始,每15秒一次。
问号(?)字符和字母L字符只有在月内日期和周内日期字段中可用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入"?",表示周内日期值无关紧要。这里有个很蛋疼的设定,无关Quartz,而是Spring集成Quartz后,它自己加的一个约束,那就是:日期(1-31)和星期(SUN-SAT)两者,必须有一个是问号(?),系统在启动的时候,Spring会检查表达式,如果不符合它的规则,就会抛异常。所以在使用的时候这个地方一定要注意,而这个在Linux上执行Cron是没有这个限制的。
字母L字符是last的缩写。放在月内日期字段中,表示安排在当月最后一天执行。在周内日期字段中,如果"L"单独存在,就等于"7",否则代表当月内周内日期的最后一个实例。所以"0L"表示安排在当月的最后一个星期日执行。
字母(W)字符把执行安排在最靠近指定值的工作日。把"1W"放在月内日期字段中,表示把执行安排在当月的第一个工作日内。
井号(#)字符为给定月份指定具体的工作日实例。把"MON#2"放在周内日期字段中,表示把任务安排在当月的第二个星期一。
星号(*)字符是通配字符,表示该字段可以接受任何可能的值、表达式例子。
例子:
"0 0 08 * * ?" 每天上午8点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 ? * 6L 2009-2019" 2009年至2019年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
【示例】
我们使用Spring定时服务Quartz来实现一个每5秒打印一次当前时间的小例子。
1:定义接口IPrintInfoService类
1 package demoinfo.spring.quartz;
2 public interface IPrintInfoService {
3 public void print();
4 }
2:实现接口类PrintInfoServiceImpl
1 package demoinfo.spring.quartz;
2
3 import java.text.SimpleDateFormat;
4 import java.util.Calendar;
5 import java.util.Date;
6
7 import demoinfo.spring.quartz.IPrintInfoService;
8
9 public class PrintInfoServiceImpl implements IPrintInfoService{
10
11 public void print() {
12 Calendar now = Calendar.getInstance();
13 System.out.println("现在是北京时间:" + this.format(now.getTime()));
14 }
15
16 public String format(Date date){
17 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
18 return sdf.format(date);
19 }
20
21 }
3:基于QuartzJobBean的实现类PrintInfoJob
package demoinfo.spring.quartz;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import demoinfo.spring.quartz.IPrintInfoService;
public class PrintInfoJob extends QuartzJobBean{
private IPrintInfoService prinfInfoService = null;
public IPrintInfoService getPrinfInfoService() {
return prinfInfoService;
}
public void setPrinfInfoService(IPrintInfoService prinfInfoService) {
this.prinfInfoService = prinfInfoService;
}
@Override
protected void executeInternal(JobExecutionContext arg0)
throws JobExecutionException {
this.prinfInfoService.print();
}
}
4:Spring配置文件applicationContext.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:tx="http://www.springframework.org/schema/tx"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans
7 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
8 http://www.springframework.org/schema/context
9 http://www.springframework.org/schema/context/spring-context-2.5.xsd
10 http://www.springframework.org/schema/tx
11 http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
12
13
14 <bean id="printInfoService" class="demoinfo.spring.quartz.PrintInfoServiceImpl" />
15 <!-- 配置一个Job -->
16 <bean id="printInfoJob" class="org.springframework.scheduling.quartz.JobDetailBean">
17 <property name="jobClass" value="demoinfo.spring.quartz.PrintInfoJob" />
18 <property name="jobDataAsMap">
19 <map>
20 <entry key="prinfInfoService" value-ref="printInfoService"></entry>
21 </map>
22 </property>
23 </bean>
24
25 <!-- 简单的触发器 -->
26 <bean id="simplePrintInfoTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
27 <property name="jobDetail">
28 <ref bean="printInfoJob" />
29 </property>
30 <property name="startDelay">
31 <value>6000</value>
32 </property>
33 <property name="repeatInterval">
34 <value>6000</value>
35 </property>
36 </bean>
37
38 <!--复杂的触发器 -->
39 <bean id="complexPrintInfoTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
40 <property name="jobDetail">
41 <ref bean="printInfoJob" />
42 </property>
43 <property name="cronExpression">
44 <value>00,05,10,15,20,25,30,35,40,45,50,55 * * * * ?</value>
45 </property>
46 </bean>
47
48 <!-- spring触发工厂 -->
49 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
50 <property name="triggers">
51 <list>
52 <ref bean="complexPrintInfoTrigger" />
53 </list>
54 </property>
55 </bean>
56 </beans>
5:测试用例类SpringQuartzDemo
1 package demoinfo.spring.quartz;
2
3 import org.springframework.context.support.ClassPathXmlApplicationContext;
4
5 public class SpringQuartzDemo {
6
7 public static void main(String[] args) {
8 System.out.println("测试开始......");
9 new ClassPathXmlApplicationContext(
10 "classpath:demoinfo/spring/quartz/applicationContext.xml");
11 System.out.println("测试结束......");
12 }
13
14 }
运行测试用例,可以看到控制台每过5秒钟就打印一次时间信息。