利用Spring Boot轻松实现动态定时器开发!
前言
- 传统定时器是硬编码。但是有的时候业务上需要不断地调整
问题描述
- 我们开发了一个定闹钟的功能。这个功能肯定是定时器开发。但是这就存在一个问题这个定时是动态的。那么我们如何实现呢?Spring Boot核心学习笔记共享。
简介
- 定时器在开发中真的算是一种福利了。通过定时器我们省去了很多人力。我们通过定时器将一些繁琐定期的事情通过代码去完成。在Java开发中我们通过Timer类可以简单实现定时器功能。既然是springboot课程今天我们就来看看srpingboot整合定时器的事情
传统定时器
- 这里使用的是之前课程一的配置。springboot打算是系列讲解。所以配置都是承前启后的。建议大家按顺序观看。
@Component
public class SimpleSchedule {
@Autowired
TestMapper testMapper;
@Scheduled(cron = "*/6 * * * * ?")
private void process() {
List<test> tests = testMapper.getTests();
System.out.println(tests);
}
}
- 定时器的编写也很简单,只需要在类或者方法上加上@Scheduled注解。然后配置cron表达式就可以了。这里得注意一下需要在spirngboot启动类上加上开发定时器的注解。
@SpringBootApplication
public class CrontabApplication {
public static void main(String[] args) {
SpringApplication.run(CrontabApplication.class, args);
}
}
- 代码中我们使用的是最简单的一种方式。
- cron表达式:指定任务在特定时间执行
- fixedDelay:表示上一次任务执行完成后多久再执行,参数类型long,单位:ms
- fixedDelayString:与fixedDelay一样,只是参数类型是String
- fixedRate:表示按一定的频率执行任务,参数类型long,单位:ms 如: fixedRate(5000),表示这个定时器任务每5秒执行一次
- fixedRateString:与fixedRate一样,只是参数类型变为String
- initialDelay:表示延迟多久再第一次执行任务,参数类型为long ,单位:ms
- initialDelayString:与initialDelay一样,只是参数类型String
动态定时器
- 上面的定时器已经成功的配置了。但是现在有一个需求客户想通过页面定制配置定时器执行的频率。上面代码我们是写死6S执行一次。如果客户想通过可视化配置。配置完成之后我总不能再手动改写代码吧。那么动态定时器就产生了。
V1.0
- 既然动态我们就得将客户配置的数据进行本地化。当然是存储在数据库中。
- 对应的我们新建Mapper查询定时任务信息。因为这里只配置了表达式。没有配置表达式对应的定时器。也是为了测试。这里默认表达式就是一个。
@Configuration
public class ScheduleConfigV1 implements SchedulingConfigurer {
@Autowired
CronMapper cronMapper;
@Autowired
TestMapper testMapper;
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(()-> {
System.out.println("执行定时器任务:" + LocalDateTime.now().toLocalTime());
List<test> tests = testMapper.getTests();
System.out.println(tests);
},
triggerContext -> {
List<cron> crons = cronMapper.getCron();
Cron cron = crons.get(0);
return new CronTrigger(cron.getCron()).nextExecutionTime(triggerContext);
});
}
}
- 执行这个代码我们最好先关掉前面那个静态的定时器。这样看的效果明显点。首先数据库配置的是6秒执行一次。然后把数据改成2秒执行一次。看看效果。
- 我们发现只要数据库信息修改了。定时任务会自动修改频率的。最重要的是不需要重启我们的代码。
- 上面虽然是动态配置了。但是有一个缺点。就是修改之后生效是在下一次触发定时器执行后有效。说白了就是一开始一小时执行一次,在这期间修改了不能立马生效必须得到下一次一小时才会去刷新配置。这里的动态可以理解成懒动态。
V2.0
- 上面的功能虽然是动态的。但是对于量产的话肯定是不科学的。首先数据库不可能只存一条数据的。
- 如果存多条数据那么多条定时规则与具体的定时器怎么进行匹配呢?
- 既然是动态的那么如何通过数据库控制定时器的开关呢?
- 定时任务的通过代码启动实际是scheduler.schedule(task, new CronTrigger("*/2 * * * * ?"));实现的。这个方法返回的对象是ScheduledFuture。通过canel方法取消定时任务。基于这两个方法我们来改进下我们之前的定时任务。
Registar
- 首先我们提供一个注册器,注册器的功能就是管理定时任务。提供增加删除功能。在增加定时器的节点上我们调用scheduler.schedule(task, new CronTrigger("*/2 * * * * ?"));来启动定时任务。在删除节点上调用之前获取的ScheduledFuture来canel这个定时任务。这样做的好处我们可以随时控制定时任务的开关
public void addCronTask(Runnable task, String cron) {
addCronTask(new CronTask(task,cron));
}
- 上面添加需要有一个runnable和cron表达式。用一个ConcurrentHashMap来管理添加进来的runnable。runnable为key,ScheduledTask为值。
public ScheduledTask scheduleCronTask(CronTask cronTask) {
ScheduledTask scheduledTask;
scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
- 这样构建一个ScheduledTask对象。
public final class ScheduledTask {
public volatile ScheduledFuture<!--?--> future;
/**
* 取消定时任务
*/
public void cancel() {
ScheduledFuture<!--?--> future = this.future;
if (future != null) {
future.cancel(true);
}
}
}
- 这样我们就可以通过构建一个runnable线程,结合表达式通过注册器注册就可以开启这个线程已固定频率执行。通过remove关闭线程。
SchedulingRunnable task = new SchedulingRunnable(TestMapper.class, "getTests", null);
cronTaskRegistrar.addCronTask(task, "0/10 * * * * ?");
关注公众号:麒麟改bug 共享Spring boot学习笔记+面试真题【附答案解析分享】
- 这样做的好处是我们可以在表数据修改的情况下立马更新定时任务规则。
总结
-上面的代码已经上传至gitee 地址:https://gitee.com/zxhTom/crontab.git
- 下面Java类是我们这次使用用到的类。
- SchedulingConfigurer
- DisposableBean
- ConcurrentHashMap