其实如果只是一个简单的动态定时任务,并不需要一堆乱七八糟的配置,本文用最简单的方法
/**
* @author linnine
*/
@Configuration
@Slf4j
public class CompleteSchedule implements SchedulingConfigurer {
@Resource
ICronService cronService;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
//添加任务内容(Runnable)
() -> startSchedule(),
//设置执行周期(Trigger)
triggerContext -> {
//根据任务类型获取任务执行时间,这个JobType是个枚举类
String cron = cronService.getCronByJobType(JobType.TEST.getCode());
//手动将获取的cron表达式转换成
Date cronDate = MyDateUtil.cronToDate(cron);
//获取当前时间
Date nowDate = new Date();
//如果小于现在,就执行0/5 一直执行。合法性校验.
if (cronDate.before(nowDate)||StringUtils.isBlank(cron)||!MatchUtil.matchCron(cron)){
log.info("每隔5秒执行一次");
cron="0/5 * * * * ?";
}
//返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}
public void startSchedule(){
log.info("开始执行任务: " + LocalDateTime.now().toLocalTime());
todo...
}
}
开始说坑
-
一个类只能做一个任务,如果要多任务,将要写多个类,因为是需要实现类,并重写该方法来执行任务的。
-
一旦设定的执行时间是过去的话,就很难执行,且无法手动开启,只能重启(如果有,那就是我不知道!)
因为我的定时任务是精确到指定 月 天 时 分,这四个都是指定的,所以一旦超过了这一刻,那要再执行就得等明年了,就很坑!
-
cron的日期时间不能写0几,比如我们习惯性的早上5点,会写05,但是到这里面就跑不动了!所以要把0去掉!
-
因为前面的精确,所以0/5,每5秒执行一次虽然成功了,但因为太精确了,所以只有那一分钟这样执行了。
比如我是 0/5 30 7 6 6 意思就是6月6号 早上7点30分开始每5秒执行一次。
然后跑到31分钟就直接停了,防不胜防啊!
后面是因为感觉任务有执行,但是有没有达到预计的效果,去看了日志才发现,大意了呀!
所以上面的方法才会有一个判断,判断时间是否小于现在,如果小于现在,就开始不断的执行,执行到目标完成后,我在任务中,手写了一条判定,任务结束后,就将数据库里的表达式,改成下一次执行时间的表达式。
然后任务每5秒执行一次的时候,会反复查,发现时间超过现在了,就会应用,并等到新时间到了,才开始执行。
突然发现这样我任务时间表达式就不用写0/5了, 但是影响不大,所以还是先不改了!免得又出什么幺蛾子。
-
还有,我为什么要手写cron转换成date的方法,其实网上也是有一个方法,是提供了一个nextdate,两行代码就解决了。不过他是通过当前时间 判断返回下一次执行时间。
也是因为这个坑,我才发现我第二条不是不执行了,而是要等到明年。
所以我自己手写了一个比较靠谱,建议不要去搜了,因为都是很乱的,还是自己手写一条比较靠谱。
扩展,cron转换date
我的思路就是先将cron表达式通过空格分割,然后将这个数组便利判断,用正则表达式判断,如果不是数字就返回00,如果是一个数字就加0再返回,如果是2个数字就直接返回。
这里加0,是因为要转换成日期!
看数字长度是否为7,如果不是就手动获取一下现在的年份。
因为cron表达式是这样的,秒 分 时 日 月 星期 年(可写/可不写)
没错第6个不是年,是星期,一般都填的问号,除非你有特殊需求。
然后将这些字符串拼接成目标格式,再配合这个格式去转换成日期。