在我们开发项目中,定时任务是经常用到的一种技术,来处理一些业务,SpringBoot 默认支持定时任务,怎么样是不是感觉 Spring Boot 太人性化了,那么下面我们看一下怎么实现一个定时器吧。

Tips
开发 Spring Boot 项目有个口诀或者说是 Spring Boot 的三板斧:加依赖、写配置、添注解
示例代码详见:https://github.com/dddreams/learn-spring-boot/tree/master/spring-boot-schedule

加依赖

Spring Boot 默认支持定时任务,所以只要加入 Spring Boot 的依赖即可

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

写配置

实际开发中一般将经常变化的值配置到配置文件中,定时任务中我们可以自定义配置项 cron 来控制定时任务执行的时间

1
2
3
4
5
6
7
server:
port: 8080
spring:
application:
name: schedule
schedule:
cron: 0 */1 * * * ? # 表达式表示 1 分钟执行一次

关于 cron 表达式,如果不清楚请先百度。

添注解

我们需要在启动类上加 @EnableScheduling 注解即可开启定时任务

1
2
3
4
5
6
7
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

编写定时任务的代码

1
2
3
4
5
6
7
8
9
@Slf4j
@Component
public class TestTasks {
//@Scheduled(cron = "0 */1 * * * ?")
@Scheduled(cron = "${schedule.cron}")
public void testTask() {
log.info("哈哈,我执行了!");
}
}

启动项目,便会在控制台隔1分钟打印

1
2
3
4
哈哈,我执行了!
哈哈,我执行了!
哈哈,我执行了!
...

注意: @Component 注解是启动后立即执行,${schedule.cron} 便是从配置文件读取配置的执行时间。

前面说了如果你对 cron 不了解,不用担心,Spring boot 提供了另外的方式 fixedRate,详解如下:

1
2
3
@Scheduled(fixedRate = 60000) :上一次开始执行时间点之后1分再执行
@Scheduled(fixedDelay = 60000) :上一次执行完毕时间点之后1分再执行
@Scheduled(initialDelay=1000, fixedRate=60000) :第一次延迟1秒后执行,之后按 fixedRate 的规则每1分执行一次

代码:

1
2
3
4
@Scheduled(fixedRate = 60000)
public void testTask1() {
log.info("哈哈,我又执行了!");
}

结果:

1
2
3
4
5
哈哈,我执行了!
哈哈,我又执行了!
哈哈,我执行了!
哈哈,我又执行了!
...

动态添加或删除定时任务

在实际项目中,往往会遇到动态的添加或停止定时任务,来我们看看怎么实现,首先添加一个配置类

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
@Configuration
public class DefaultSchedulingConfigurer implements SchedulingConfigurer {

private ScheduledTaskRegistrar taskRegistrar;
private Set<ScheduledFuture<?>> scheduledFutures = null;
private Map<String, ScheduledFuture<?>> taskFutures = new ConcurrentHashMap<>();

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
//System.out.println(inited());
}

@SuppressWarnings("unchecked")
private Set<ScheduledFuture<?>> getScheduledFutures() {
if (scheduledFutures == null) {
try {
// spring版本不同选用不同字段scheduledFutures
scheduledFutures = (Set<ScheduledFuture<?>>) BeanUtils.getProperty(taskRegistrar, "scheduledTasks");
} catch (NoSuchFieldException e) {
throw new SchedulingException("not found scheduledFutures field.");
}
}
return scheduledFutures;
}

/**
* 添加任务
*/
public void addTriggerTask(String taskId, TriggerTask triggerTask) {
if (taskFutures.containsKey(taskId)) {
throw new SchedulingException("the taskId[" + taskId + "] was added.");
}
TaskScheduler scheduler = taskRegistrar.getScheduler();
ScheduledFuture<?> future = scheduler.schedule(triggerTask.getRunnable(), triggerTask.getTrigger());
getScheduledFutures().add(future);
taskFutures.put(taskId, future);
}

/**
* 取消任务
*/
public void cancelTriggerTask(String taskId) {
ScheduledFuture<?> future = taskFutures.get(taskId);
if (future != null) {
future.cancel(true);
}
taskFutures.remove(taskId);
getScheduledFutures().remove(future);
}

/**
* 重置任务
*/
public void resetTriggerTask(String taskId, TriggerTask triggerTask) {
cancelTriggerTask(taskId);
addTriggerTask(taskId, triggerTask);
}

/**
* 任务编号
*/
public Set<String> taskIds() {
return taskFutures.keySet();
}

/**
* 是否存在任务
*/
public boolean hasTask(String taskId) {
return this.taskFutures.containsKey(taskId);
}

/**
* 任务调度是否已经初始化完成
*/
public boolean inited() {
return this.taskRegistrar != null && this.taskRegistrar.getScheduler() != null;
}
}

然后新建一个 controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/task")
public class TestTaskController {
@Autowired
private DefaultSchedulingConfigurer defaultSchedulingConfigurer;
@GetMapping("/add")
public String add(@RequestParam(name = "name") String name) {
defaultSchedulingConfigurer.addTriggerTask(name,
new TriggerTask(
() -> System.out.println("hello world!"),
new CronTrigger("0/5 * * * * ? ")));//5秒执行一次
return "任务开启成功";
}
@GetMapping("/del")
public String del(@RequestParam(name = "name") String name) {
defaultSchedulingConfigurer.cancelTriggerTask(name);
return "任务删除成功";
}
}

请求 http://localhost:8080/task/add,定时任务即可开启
请求 http://localhost:8080/task/del,定时任务就删除成功了

最后

其实 Spring boot 的定时任务相对比较简单,如果在高并发集群环境下,我们尽量使用框架来支撑我们的业务,下一节我会介绍定时任务的框架 quartz ,大家敬请期待吧。

参考与相关链接

示例代码:https://github.com/dddreams/learn-spring-boot/tree/master/spring-boot-schedule
Spring boot 定时任务:http://www.ityouknow.com/springboot/2016/12/02/spring-boot-scheduler.html
SpringBoot+schedule+可以动态添加或删除定时任务:https://blog.csdn.net/nicky_lc/article/details/106961779

ddAnswer

更多文章请关注微信公众号: zhiheng博客

如果文章对你有用,转发分享、点赞赞赏才是真爱 [斜眼笑]