前言
- 傳統定時器是硬編碼。但是有的時候業務上需要不斷地調整
問題描述
- 我們開發了一個定鬧鐘的功能。這個功能肯定是定時器開發。但是這就存在一個問題這個定時是動態的。那么我們如何實現呢?請接著看
簡介
- 定時器在開發中真的算是一種福利了。通過定時器我們省去了很多人力。我們通過定時器將一些繁瑣定期的事情通過代碼去完成。在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 * * * * ?");
- 這樣做的好處是我們可以在表數據修改的情況下立馬更新定時任務規則。
總結
-上面的代碼已經上傳至gitee
點我傳送
https://gitee.com/zxhTom/crontab.git
- 下面Java類是我們這次使用用到的類。
- SchedulingConfigurer
- DisposableBean
- ConcurrentHashMap
原文鏈接:
https://www.cnblogs.com/zhangxinhua/p/14830103.html
如果覺得本文對你有幫助,可以轉發關注支持一下