定时任务,顾名思义就是把任务定在某个时间点去做,可能是一次执行,也可能是循环往复,无论是那种形式,在如今都是非常的简单。
在Java中,Executors中的newSingleThreadScheduledExecutor(),以及Timer都可以实现定时功能,但是它们只能实现一些简单的定时任务,如果我们想实现类似cron表达式的复杂定时任务,它们就显得有些力不从心了。SpringBoot针对定时任务做了很好的封装,我们只需要进行简单的配置就可以实现。
SpringBoot实现一个简单的定时任务只需要两步,如下所示:
第一步,启动类上加上@EnableScheduling注解,
@SpringBootApplication(scanBasePackages = {"com.fanqie.springboot2"})
@EnableScheduling //表示开启定时任务
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
第二步:编写定时任务类,大家记得@Component注解一定要加上,我遇到身边的同事就是因为大意,没有加上该注解,导致SpringBoot不能扫描到该类,定时任务没有执行。
@Component
public class SimpleScheduler {
/**
* 每10秒执行一次定时任务
*/
@Scheduled(cron = "*/10 * * * * ?")
public void firstTask() {
System.out.println("当前时间:" + LocalDateTime.now());
}
}
到这里,我们就实现了一个简单的定时任务。@EnableScheduling 和@Scheduled,利用cron表达式,就可以实现各种复杂的定时任务。大家是不是觉得非常简单呢?是的,就是这么简单,利用SpringBoot提供的定时任务,我们就可以实现大部分的业务,不需要去额外配置quartz来完成,@Scheduled里面还包含了许多其它的参数,例如:fixedDelay(上一次任务执行完成后,再等待fixedDelay时长后,执行下一次任务)、fixedRate(任务执行时开始计时,若任务运行时长>fixedRate,下一次任务在上一次任务执行完成后立即执行)、initialDelay(初始化等待时间),为了防止篇幅过长,这里我就不写示例了,大家可以自行尝试一下。
现在可以完成大部分的业务需求了,但是现在,我们有这样的一个需求:在web端显示定时任务列表,并且可以实现定时任务的开启和关闭。大家思考两分钟...
大家可以发现,通过@EnableScheduling 和@Scheduled实现的定时任务,参数都是预先定义好的,而且我们不能控制它们的开启和关闭,当程序运行的时候,它们就已经启动了。我们不可能在开启的时候就把程序运行,关闭的时候就把程序杀掉吧,这样会让同行笑掉大牙的,而且还可能出现数据不一致的现象。
其实在SpringBoot中,ThreadPoolTaskScheduler就已经很好的集成了该功能,只需要我们写一些简单的代码,就可以实现自我控制,如何实现呢?请往下看:
第一步:ThreadPoolTaskScheduler配置,
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setThreadFactory(r -> new Thread(r, "定时任务"));
return threadPoolTaskScheduler;
}
}
第二步:编写定时任务接口,这里为了演示方便,就不写web页面了,直接使用Swagger和List进行测试(正式项目中,直接替换为数据库或其它方式),
@RestController
@Api(tags = "定时任务模块")
@RequestMapping("/scheduler")
public class SchedulerController {
private final ThreadPoolTaskScheduler threadPoolTaskScheduler;
private List list = new ArrayList<>();
/**
* 默认启动一个定时任务
*
* @param threadPoolTaskScheduler threadPoolTaskScheduler
*/
@Autowired
public SchedulerController(ThreadPoolTaskScheduler threadPoolTaskScheduler) {
this.threadPoolTaskScheduler = threadPoolTaskScheduler;
//schedule有两个参数,Runnable task, Trigger trigger,task代表执行任务,trigger代表触发器
String time = "*/10 * * * * ?";
ScheduledFuture> future1 = this.threadPoolTaskScheduler.schedule(() -> {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + thread.getId() + ": 我还在活着");
}, new CronTrigger(time));
//保存到集合中,
SchedulerPO schedulerPO = new SchedulerPO(1, "我是定时任务一", "创建", future1, time);
list.add(schedulerPO);
}
@ApiOperation(value = "关闭定时任务", notes = "关闭定时任务")
@PutMapping("/{id}/cancel")
public void cancelById(@PathVariable int id) {
list.forEach(schedulerPO -> {
if (schedulerPO.getId() == id) {
ScheduledFuture future = schedulerPO.getScheduledFuture();
if (future != null) {
future.cancel(true);
}
}
});
}
@ApiOperation(value = "查询所有定时任务", notes = "查询所有定时任务")
@GetMapping("/all")
public List all() {
return list;
}
@ApiOperation(value = "添加定时任务", notes = "添加定时任务")
@PostMapping("/add")
public List add(@RequestBody SchedulerPO schedulerPO) {
ScheduledFuture> future = this.threadPoolTaskScheduler.schedule(() -> {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + thread.getId() + ": 我还在活着");
}, new CronTrigger(schedulerPO.getTime()));
schedulerPO.setScheduledFuture(future);
list.add(schedulerPO);
return list;
}
}
这里关于swagger的使用,就不进行说明了,如果大家不了解,可以看这篇springboot2与swagger2整合。现在我们就可以访问http://localhost:8080/swagger-ui.html来进行测试定时任务了,
首先查询所有定时任务,如下:
我们发现,当前有一个定时任务,scheduledFuture可以看出,这个定时任务正在运行。
现在,我们继续往其中添加一个定时任务(每隔2s执行一次),如下:
再来查询定时任务列表,如下:
我们可以发现,现在有两个定时任务在运行中,如果大家不相信它们在运行,也可以去跟踪后台的操作日志。两个定时任务好像有点多余了,我们关掉id为1的定时任务吧,如下:
查询定时任务列表,如下:
通过响应结果,我们可以看出,定时任务一已经被关闭了。到此为止,定时任务的启动和关闭就被我们实现了,SpringBoot已经帮我们整合了许多功能,有时候不用第三方工具,也可以实现常见的业务。在实际项目中,大家只需要把上面的List换成更加安全可靠的存储机制就可以了。
如果大家只是实现简单的定时任务,@EnableScheduling 和@Scheduled就可以满足大家的需求(cron与fixedRate 、fixedDelay不能同时使用),当然,如果大家想要实现定时任务的自我控制,SpringBoot中的ThreadPoolTaskScheduler 就可以实现效果。
详细代码可见https://gitee.com/hpaw/SpringBoot2Demo/tree/master/springboot-scheduler