Spring与Quartz整合实践

什么是Quartz?

Quartz是一款基于Java语言的作业调度框架。一般用来创建或简单或复杂的调度时间表,执行Java下任意数量的作业。

可以通过CronTrigger定义Quartz的调度时间表(例如0 0 12 ? * WED表示“每周三上午12:00”)。此外,时间表也可以通过SimpleTrigger,由Date定义触发的开始时间、毫秒的时间间隔和重复计数(例如“在下周三12:00,然后每隔10秒、执行5次”)。可以使用Calender定义例外的日程(例如“没有周末和节假日”)。

作业可以是实现了Job接口任意的Java类。作业监听器(JobListener)和触发器监听器(TriggerListener)通知作业的执行(和其他事件)。作业及其触发器可以被持久化。Quartz一般用于企业级应用程序,以支持工作流、系统管理(维护)活动,并在应用程序中提供实时的服务。Quartz还支持集群。

Quartz基本元素

从上面的介绍可以看到,Quartz主要由三个部分组成,分别是调度器,作业详情以及触发器。

调度器

调度器(Scheduler)的作用主要是通过作业详情(JobDetail)和触发器(Trigger)来执行特定的任务。调度器的生命周期从它的start()开始,一直到shutdown()方法结束。在使用调度器的时候,必须先执行start()方法,才能够开始任务的调度。当然了,在Spring当中,已经帮我们封装好了,所以直接声明一个调度器类就好了。

作业详情

先说说作业(Job)吧,作业就是我们调度器需要执行的具体任务逻辑。在实际的代码中就是指实现了org.quartz.Job接口的任何类。orf.quartz.Job接口只有一个方法void execute(JobExecutionContext context),因此任务调度时候执行的就是execute方法的逻辑。

那么作业详情(JobDetail)又是什么呢?作业详情其实是对作业的一个封装,会指定它的名称(name)和组别(group)。这两个属性非常重要,因为这两个属性是这个作业详情在整个调度系统中的唯一编号。我们可以通过作业详情向作业中传入一些数据(JobDataMap),在运行时能通过execute方法的context参数获取到。context就是上下文参数,用于获取作业详情传递过来的数据。

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
// define the job and tie it to our DumbJob class
JobDetail job = newJob(DumbJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();

public class DumbJob implements Job {

public DumbJob() {
}

public void execute(JobExecutionContext context)
throws JobExecutionException
{
JobKey key = context.getJobDetail().getKey();

JobDataMap dataMap = context.getJobDetail().getJobDataMap();

String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");

System.err.println("Instance " + key + " of DumbJob says: "
+ jobSays + ", and val is: " + myFloatValue);
}
}

触发器

触发器(Trigger)是什么呢?其实在传统的Java调度系统中,我们直接在调度器中指定执行时间,执行频率就可以了。但是在Quartz中,开发者将调度器的执行和触发两个步骤解耦了。这样做的好处是可以更加灵活地配置各种需求的任务。所以,触发器只是一个引起任务执行的条件。

触发器主要用于指定任务执行的时间,频率以及重复次数等等。

1
2
3
4
5
6
7
8
9
10
11
12
HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate( someDate );
cal.addExcludedDate( someOtherDate );

sched.addCalendar("myHolidays", cal, false);

Trigger t = newTrigger()
.withIdentity("myTrigger")
.forJob("myJob")
.withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();

Quartz使用方式

下面给出一个Quartz的使用例子,纯Java客户端实现。

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
package com.qinjiangbo.scheduler;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
* @date: 06/12/2017 9:40 PM
* @author: [email protected]
* @description:
*/
public class ProxyDumpJob {

// 先做一个Demo
public static void main(String[] args) {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
// 写一些逻辑
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("firstJob", "qinjiangbo")
.build();

// 设置触发器
Trigger trigger = newTrigger()
.withIdentity("firstTrigger", "qinjiangbo")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();

// 启动定时任务
scheduler.scheduleJob(jobDetail, trigger);

} catch (SchedulerException e) {
e.printStackTrace();
}
}
}

HelloJob.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qinjiangbo.scheduler;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;

import java.util.Date;

/**
* @date: 07/12/2017 10:44 AM
* @author: [email protected]
* @description:
*/
public class HelloJob implements Job {

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println(new Date().toString());
}
}

与Spring集成

和Spring的集成和这个过程比较相似,只不过作业详情和触发器的实例化都是通过Spring的Bean工厂实现的了。

maven配置

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
49

<!-- 定时任务工具 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>

<!-- Spring框架 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>

Quartz的Spring配置

创建spring-quartz.xml文件,如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- IP代理Dump任务 -->
<bean id="proxyDumpJob"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="firstJob"/>
<property name="group" value="acs"/> <!-- 组名可以随便取 -->
<property name="durability" value="true"/>
<property name="jobClass" value="com.qinjiangbo.scheduler.AcsProxyDumpJob"/>
</bean>

<!-- IP代理Dump任务触发器 -->
<bean id="proxyDumpTrigger"
class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="name" value="firstTrigger"/>
<property name="group" value="acs"/>
<property name="startTime" ref="now"/>
<!-- 注意:单位是毫秒 -->
<property name="repeatInterval" value="5000"/>
<property name="jobDetail" ref="proxyDumpJob"/>
</bean>
<bean id="now" class="java.util.Date"/>

<!-- 全局定时任务调度器 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="proxyDumpJob"/>
</list>
</property>
<property name="triggers">
<list>
<ref bean="proxyDumpTrigger"/>
</list>
</property>
</bean>
</beans>

作业类

AcsProxyDumpJob.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.qinjiangbo.scheduler;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;

/**
* @date: 07/12/2017 10:44 AM
* @author: [email protected]
* @description:
*/
public class AcsProxyDumpJob implements Job {

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println(new Date().toString());
}
}

任务执行情况

启动tomcat服务器,可以看到控制台的情况如下:

1
2
3
4
5
6
7
8
9
10
11
...
2017-12-08 13:40:55 [INFO] - org.springframework.scheduling.quartz.SchedulerFactoryBean - SchedulerFactoryBean.java(645) - Starting Quartz Scheduler now
2017-12-08 13:40:55 [INFO] - org.quartz.core.QuartzScheduler - QuartzScheduler.java(547) - Scheduler org.springframework.scheduling.quartz.SchedulerFactoryBean#0_$_NON_CLUSTERED started.
Fri Dec 08 13:40:55 CST 2017
2017-12-08 13:40:55 [INFO] - org.springframework.web.servlet.DispatcherServlet - FrameworkServlet.java(508) - FrameworkServlet 'servlet': initialization completed in 9163 ms
08-Dec-2017 13:40:57.192 INFO [http-nio-8080-exec-2] org.apache.tomcat.util.http.parser.Cookie.logInvalidHeader A cookie header was received [1512543852,1512550588,1512666728; Hm_lpvt_a0e256d0086924d62a9116fdbab18bdf=1512668909; __cfduid=dcd329b1d40b3086b0603537f1e8222d81512702662] that contained an invalid cookie. That cookie will be ignored.Note: further occurrences of this error will be logged at DEBUG level.
Fri Dec 08 13:41:00 CST 2017
Fri Dec 08 13:41:05 CST 2017
Fri Dec 08 13:41:10 CST 2017
...
...

可以看到项目成功启动,任务也按照预先设定的成功执行。

总结

Quartz作为一款非常优秀的调度框架提供了非常丰富的功能。但是也存在一些先天的不足,比如没有直接提供监控台,而阿里的SchedulerX做的比这个要好,目前已经在阿里云上推出了相关的产品,感兴趣的可以去了解一下。关于Quartz的系统架构以及Quartz和Spring Scheduling以及Java的调度系统等等这些调度器之间的区别,我会在后面的文章中陆续写到,欢迎大家持续关注。

参考资料https://en.wikipedia.org/wiki/Quartz_(scheduler)

分享到