不想说话,自闭中..

学习Quartz调度框架

前言

Quartz的应用几乎涵括每一个公司,你可以在生活的轻易的发现对它的应用:公司定时生成报表、每月提醒你还信用卡..所以Quartz是什么呢?

Quartz是一个任务调度框架,即在给定的触发条件(时间)下,触发相应的任务(Job)起来干活~

Quartz框架的重要组成部分

  • Job:它是一个接口,它只定义了一个execute(JobExecutionContext context)方法;编写实现类的时候需要在execute方法中定义要定时执行的Job,JobExecutionContext提供了一些调度信息(任务执行的上下文),这些信息在运行时将保存在JobDetail实例中。
  • JobDetail:用于绑定相应Job的实例;Quartz在每次调度Job时,都需要创建一个Job实例,所以它不直接接受一个Job实例,相反它会结合一个实现类(JobDetail:描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息),以便运行时通过newInstance()的反射机制实例化Job)。
  • JobDataMap:其中包含一些序列话的对象信息。可以通过JobExecutionContext接口中的getMergedJobDataMap()方法获取JobDetail的一些信息。
  • JobBuilder:用于创建JobDetail的实例(建造者模式)。
  • JobStore:接口,用于负责跟踪给Scheduler的工作数据:Job、Trigger..,保存数据到数据库/内存中。
  • Trigger:触发器,负责触发执行Job的触发规则。主要有SimpleTrigger和CronTrigger两个子类。当需要执行一次或者固定时间间隔的任务时,前者是你的最佳选择;当需要执行更为复杂的任务时,后者足够全面。具体的触发规则将在下文讲解。
  • TriggerBuilder:用于创建Triiger实例(建造者模式)。
  • ThreadPool:线程池,用于共享线程,解决并发的问题。
  • Scheduler:调度器,Quartz独立运行的容器,JobDetail和Trigger可以注册到Scheduler中,两者在Scheduler中拥有唯一的标识,外界可以通过组/名称的方式来进行访问与控制。
  • Calendar:它是一些日历特定时间点的集合,一个Trigger可以和多个Calendar关联,以排除或包含某些时间点(例如:遇到一些节假日的情况)。
  • 监听器:JobListener、TriggerListener、SchedulerListener他们可以用于监听任务的发生,执行相关操作。

Quartz框架核心

其实Quartz框架最重要三个部分便是:JobDetail(具体做啥子任务)、Trigger(执行该任务的条件)、Scheduler(调度作用)。
其中,我觉得可能需要重点掌握的便是Cron表达式了。

  • Cron表达式

    Cron表达式的格式:秒 分 时 日 月 周 年(可选)。

字段名 允许的值 允许的特殊字符
0-59 , – * /
0-59 , – * /
小时 0-23 , – * /
1-31 , – * ? / L W C
1-12 or JAN-DEC , – * /
1-7 or SUN-SAT , – * ? / L C # MON FRI
年 (可选字段) empty, 1970-2099 , – * /
1
2
3
4
5
6
7
“?”字符:表示不确定的值
“,”字符:指定数个值
“-”字符:指定一个值的范围
“/”字符:指定一个值的增加幅度。n/m表示从n开始,每次增加m
“L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X
“W”字符:指定离给定日期最近的工作日(周一到周五)
“#”字符:表示该月第几个周X。6#3表示该月第3个周五
1
2
3
4
5
6
7
8
9
10
Cron表达式范例:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

看起来有点复杂,其实我不会告诉你有一个网站可以帮你写Cron表达式的:)

代码实现

下面介绍一个入门的小Demo:

  • 创建HelloJob类实现Job接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloJob implements Job{
/**
* JobExecutionContext包含Job执行的上下文(传参)
* JobExecutionException当任务执行失败后,可能会出现此异常
* Job生命周期是创建Job实例后会被执行,之后后被垃圾回收
*/
public void execute(JobExecutionContext context) throws JobExecutionException {
//编写具体的业务逻辑
JobKey key = context.getJobDetail().getKey();
System.out.println("My Job name and group are:"+key.getName()+","+key.getGroup());
TriggerKey trkey = context.getTrigger().getKey();
System.out.println("My Trigger name and group are:"+trkey.getName()+","+trkey.getGroup());

}
  • 创建 JobDetail、Trigger、Scheduler实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class HelloScheduler {

public static void main(String[] args) throws SchedulerException {
/**
* JobDetail来创建Job实例,并可以携带一些Job没有携带但又需要的信息
* 属性:name(任务名称,必须),group(默认值DEDAULT组,必须),JobClass(任务的实现类,必须),JobDataMap(传参的作用,键值对,可以从JobExecutionContext中获取)
* 监听器..
*/
//创建一个JobDetail实例,用于绑定HelloJob Class(链式写法)
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.usingJobData("message", "helloMyJob1").build();//定义唯一标识
/* //以后写日志的时候或许可以派上用场
System.out.println(jobDetail.getKey().getClass());
System.out.println(jobDetail.getKey().getName());
System.out.println(jobDetail.getKey().getGroup());*/
//CronTrigger每秒钟触发一次任务,核心在于cron表达式
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.withSchedule(
CronScheduleBuilder.cronSchedule("* * * * * ? *"))//*表示每,?表示没用
.build();

//创建Scheduler实例(Quartz要素三大要素之一)(通过工厂模式创建)
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail, cronTrigger);//绑定JobDetail和Trigger,返回最近一次将要执行的时间
}

}
  • 运行结果

    每一秒种执行一次任务:

结果运行图

  • quartz.properties

    这个是Quartz的配置文件,quartz.jar下本身就有,如果需要单独设置可以copy一份到classpath路径下,添加或者修改属性就好,项目启动会优先读取classpath下的quartz.properties;如果没有的话,Quartz默认会读取自带的quartz.properties文件。
    一般来讲,这个文件不需要修改,可能需要修改的便是org.quartz.threadPool.threadCount线程池数量,可以设置10/20/50..,不超过100,具体看实际的项目。

    配置文件


Quartz与Spring整合

Quartz和Spring的整合步骤还是要抓住Quartz的三大核心:JobDetail、Trigger、Scheduler.
只不过写在Spring配置文件里而已。

1
2
3
4
5
6
7
8
@Component("myBean")
public class MyBean {
public void printMessage(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("MyBean Executes!"+sdf.format(date));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component("anotherBean")
public class AnotherBean {
public void printAnotherMessage(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("anotherBean Executes!"+sdf.format(date));
}
}
public class FirstScheduledJob extends QuartzJobBean{
private AnotherBean anotherBean;
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}

@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("FirstScheduledJob Execute!"+sdf.format(date));
this.anotherBean.printAnotherMessage();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
       <!-- Quartz的三大要素之一-jobDetail(两种方式) -->
<!-- 1.MethodInvokingJobDetailFactoryBean 调用特定的Bean很方便 -->
<bean id="simpleJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="myBean"></property>
<property name="targetMethod" value="printMessage"></property>
</bean>
<!-- 2.JobDetailFactoryBean 支持传入一些参数,更加灵活 -->
<bean id="firstComplexJobDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass"
value="com.cfx.springquartz.quartz.FirstScheduledJob"></property>
<property name="jobDataMap">
<map>
<entry key="anotherBean" value-ref="anotherBean" ></entry>
</map>
</property>
<property name="durability" value="true"></property>
</bean>

<!-- Quartz的三大要素之二-Trigger(SimpleTrigger和CronTrigger) -->
<!-- SimpleTrigger 距离当前时间一秒后执行,之后每隔两秒执行 -->
<bean id="mySimpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="simpleJobDetail"></property>
<property name="startDelay" value="1000"></property>
<property name="repeatInterval" value="2000"></property>
</bean>
<!-- CronTrigger 每隔五秒执行 -->
<bean id="myCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="firstComplexJobDetail"></property>
<property name="cronExpression" value="0/5 * * ? * * "></property>
</bean>

<!-- Quartz的三大要素之三-Scheduler(注册JobDetail和Trigger) -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="simpleJobDetail"/>
<ref bean="firstComplexJobDetail"/>
</list>
</property>
<property name="triggers">
<list>
<ref bean="mySimpleTrigger"/>
<ref bean="myCronTrigger"/>
</list>
</property>
</bean>

运行结果:

结果运行图spring

源码下载