本文于 1502 天之前发表,文中内容可能已经过时。

引言

在企业开发中,经常会遇到时间任务调度的需求,比如每天凌晨生成前天报表、数据汇总等动态配置是否开启定时的任务。在 Java 领域中,定时任务的开源工具也非常多,小到一个 Timer 类,大到 Quartz 框架。在 Spring 中最常见的定时任务方式属 Spring schedule 注解的方式和利用 Quartz 动态管理定时任务。总体来说,个人比较喜欢的还是 Quartz,功能强大而且使用方便。

Spring-@scheduled

对于较简单的任务可以使用 Spring 内置的定时任务方法 @scheduled 注解进行配置达到自己的需求。

spring 配置文件

配置 spring 项目的基础文件 spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns:task="http://www.springframework.org/schema/task"
xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd">

<!-- 开启定时任务 spring的定时任务默认是单线程,多个任务执行起来时间会有问题,所以这里配置了线程池-->
<task:executor id="executor" pool-size="5" />
<task:scheduler id="scheduler" pool-size="10" />
<task:annotation-driven executor="executor" scheduler="scheduler" />

</beans>

Task 任务类

定义了一个任务类 ATask,里面有两个定时任务 aTask 和 bTask。编写 java 业务代码,需要在类声明上边添加 **@Component 注解,并在需要定时任务执行的方法声明上添加 @Scheduled** 注解以及 cron 表达式和相关的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定时器的任务方法不能有返回值
@Component
public class ATask {

@Scheduled(cron = "0/10 * * * * ? ") // 每10秒执行一次
public void aTask() {
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(DateTime.now().toDate()) + "*********A任务每10秒执行一次进入测试");
}

@Scheduled(cron = "0/5 * * * * ? ") // 每5秒执行一次
public void bTask() {
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(DateTime.now().toDate()) + "*********B任务每5秒执行一次进入测试");
}
}

运行结果

启动项目会发现定时任务已经开启。

Spring-Quartz

@scheduled 固然可以实现定时任务,但是仔细想想并不灵活,任务随着应用的启动而执行,并不能动态的进行管理,很是不方便,然而 Quartz 很好的解决了这一问题。

引入依赖

1
2
3
4
5
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>

任务管理类 QuartzManager

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
public class QuartzManager {

private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();

/**
* @Description: 添加一个定时任务
*
* @param jobName
* 任务名
* @param jobGroupName
* 任务组名
* @param triggerName
* 触发器名
* @param triggerGroupName
* 触发器组名
* @param jobClass
* 任务
* @param cron
* 时间设置,参考quartz说明文档
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName,
Class jobClass, String cron) {
try {
Scheduler sched = schedulerFactory.getScheduler();
// 任务名,任务组,任务执行类
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();

// 触发器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
// 触发器名,触发器组
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 触发器时间设定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
// 创建Trigger对象
CronTrigger trigger = (CronTrigger) triggerBuilder.build();

// 调度容器设置JobDetail和Trigger
sched.scheduleJob(jobDetail, trigger);

// 启动
if (!sched.isShutdown()) {
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* @Description: 修改一个任务的触发时间
*
* @param jobName
* @param jobGroupName
* @param triggerName
* 触发器名
* @param triggerGroupName
* 触发器组名
* @param cron
* 时间设置,参考quartz说明文档
*/
public static void modifyJobTime(String jobName, String jobGroupName, String triggerName, String triggerGroupName,
String cron) {
try {
Scheduler sched = schedulerFactory.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
if (trigger == null) {
return;
}

String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cron)) {
/** 方式一 :调用 rescheduleJob 开始 */
// 触发器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
// 触发器名,触发器组
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 触发器时间设定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
// 创建Trigger对象
trigger = (CronTrigger) triggerBuilder.build();
// 方式一 :修改一个任务的触发时间
sched.rescheduleJob(triggerKey, trigger);
/** 方式一 :调用 rescheduleJob 结束 */

/** 方式二:先删除,然后在创建一个新的Job */
// JobDetail jobDetail =
// sched.getJobDetail(JobKey.jobKey(jobName, jobGroupName));
// Class<? extends Job> jobClass = jobDetail.getJobClass();
// removeJob(jobName, jobGroupName, triggerName,
// triggerGroupName);
// addJob(jobName, jobGroupName, triggerName, triggerGroupName,
// jobClass, cron);
/** 方式二 :先删除,然后在创建一个新的Job */
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* @Description: 移除一个任务
*
* @param jobName
* @param jobGroupName
* @param triggerName
* @param triggerGroupName
*/
public static void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
try {
Scheduler sched = schedulerFactory.getScheduler();

TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);

sched.pauseTrigger(triggerKey);// 停止触发器
sched.unscheduleJob(triggerKey);// 移除触发器
sched.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任务
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* @Description:启动所有定时任务
*/
public static void startJobs() {
try {
Scheduler sched = schedulerFactory.getScheduler();
sched.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* @Description:关闭所有定时任务
*/
public static void shutdownJobs() {
try {
Scheduler sched = schedulerFactory.getScheduler();
if (!sched.isShutdown()) {
sched.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

任务执行业务

这里做一个简单的演示,只实现 Job 接口打印当前时间。

1
2
3
4
5
6
7
public class MyJob implements Job{

public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(DateTime.now().toDate()));
}
}

测试动态定时任务

新建 QuartzTest.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
public class QuartzTest {
public static String JOB_NAME = "动态任务调度";
public static String TRIGGER_NAME = "动态任务触发器";
public static String JOB_GROUP_NAME = "XLXXCC_JOB_GROUP";
public static String TRIGGER_GROUP_NAME = "XLXXCC_JOB_GROUP";

public static void main(String[] args) {
try {
System.out.println("【系统启动】开始(每1秒输出一次)...");
QuartzManager.addJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, MyJob.class,"0/1 * * * * ?");

Thread.sleep(5000);
System.out.println("【修改时间】开始(每5秒输出一次)...");
QuartzManager.modifyJobTime(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, "0/5 * * * * ?");

Thread.sleep(15000);
System.out.println("【移除定时】开始...");
QuartzManager.removeJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME);
System.out.println("【移除定时】成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}

输出如下:

总结

通过以上测试可以明显的看出两者的优劣,Quartz 足够灵活强大,但 Spring scheduled 在简单任务下也是一个不错的选择。