环境:
SpringBoot 2.X
JDK 8
Maven 3.5.X
OK,开始正文. . .
因为目标是实现基本的定时任务,另外ToB且为单体,所以可以忽略并发,那么这种场景的话引入第三方Quartz框架(如XXL-Job)有些多余,所以我们从框架本身出发,看是否有合适的包供我们使用(需要做调研),那么结果是好的,我们可以借助org.springframework.scheduling
来实现我们的需求。
看到上边的图片我想大家有了一些想法,是的没错,我们可以通过注解方式@Scheduled
来实现,也可以通过实现SchedulingConfigurer
接口来实现。
首先来实现第一种,没有任何难度,如下:
@Component
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class ScheduleTask {
//3.添加定时任务
@Scheduled(cron = "0/1 * * * * ?")
//或直接指定时间间隔,例如:1秒
//@Scheduled(fixedRate=1000)
private void configureTasks() {
System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
}
}
我说的没错吧,一点难度没有,但是也有需要注意的小Tips。着重看一下@Scheduled
属性值,心疼你们我把源码扒下来说一下各部分吧。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) // 这是第一个需要注意的点,这个注解只加在方法上或者注解之上生效
@Retention(RetentionPolicy.RUNTIME) // 并且是运行时
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
/**
* 定时禁用标志。
*/
String CRON_DISABLED = ScheduledTaskRegistrar.CRON_DISABLED;
/**
* 定义定时执行时间,默认值"",表示该值无效。
*/
String cron() default "";
/**
* 定义时区,默认值""。
*/
String zone() default "";
/**
* 定义任务下次开始执行的间隔时间,从上一次任务执行完成开始计算,单位毫秒。默认值-1L,表示该值设置无效。
*/
long fixedDelay() default -1;
/**
* 定义任务下次开始执行的间隔时间,从上一次任务执行完成开始计算,单位毫秒。与fixedDelay不同只在于值的格式。默认值"",表示该值设置无效。
*/
String fixedDelayString() default "";
/**
* 定义每两次任务的间隔频率,从上一次任务开始执行开始计算,单位毫秒。默认值-1L,表示该值无效。
*/
long fixedRate() default -1;
/**
* 定义每两次任务的间隔频率,从上一次任务开始执行开始计算,单位毫秒。与fixedRate不同只在于值的格式。默认值"",表示该值无效。
*/
String fixedRateString() default "";
/**
* 定义第一次执行的延迟执行时间,单位秒。默认值-1L,表示没有延迟。
*/
long initialDelay() default -1;
/**
* 定义第一次执行的延迟执行时间,与initialDelayDelay()不同在于这里使用表达式,而不是以秒为单位。默认值"",表示没有延迟。
*/
String initialDelayString() default "";
}
其中有一个属性是我们大多数(少数不知道)下要传的,那就是cron
,这个属性直接关系到这个任务什么时候执行。cron
是一个字符串表达式。
cronExpression定义时间规则,Cron表达式由6或7个空格分隔的时间字段组成:秒 分钟 小时 日期 月份 星期 年(可选)
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 1-12 | , - * / |
星期 | 1-7 | , - * ? / L C # |
年 | 1970-2099 | , - * / |
http://blog.csdn.net/supingemail/article/details/22274279
表达式生成网址:https://cron.qqe2.com/
注解实现方式讲的差不多了,那么接下来就说一下基于SchedulingConfigurer
接口的实现方式
先来一个❓,为什么有了注解这种简单的方式还要有接口方式实现呢?答案很简单,因为注解方式满足不了所有的需求。他的执行频率(或者说任务的执行周期)是固定的(有Cron表达式决定)
接口方式实现如下:
@Slf4j
@Component
@Configuration // 1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class MessageScheduleTask implements SchedulingConfigurer {
private final TrainingService trainingService;
public MessageScheduleTask(TrainingService trainingService) {
this.trainingService = trainingService;
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(
() -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),
triggerContext -> {
// 业务代码
Workitem workitem =
trainingService.getWorkitem(
new QueryWrapper<Workitem>()
.eq("id", "60bdee1ed7a2b72a6f7c1916"));
String date = workitem.getDate();
String start = workitem.getStart();
String end = workitem.getEnd();
String endDateTime = date + " " + end;
String cron = null;
try {
cron = CronUtil.getCron(endDateTime);
} catch (ParseException e) {
log.info("时间格式转换失败,请手动补偿 " + workitem.getId());
e.printStackTrace();
}
// String cron = "0/5 * * * * ?";
return new CronTrigger(Objects.requireNonNull(cron)).nextExecutionTime(triggerContext);
});
}
}
/** @author sunshaocong */
public class CronUtil {
private static final ThreadLocal<DateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm"));
private static final ThreadLocal<DateFormat> FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("ss mm HH dd MM ?"));
/**
* 日期转cron表达式格式
*
* @param date 日期
* @return 日期表达式格式
*/
public static synchronized String formatDateByPattern(String date) throws ParseException {
Date realDate = DATE_FORMAT.get().parse(date);
return FORMAT.get().format(realDate);
}
/**
* 获取cron表达式
*
* @param date 日期
* @return cron表达式
*/
public static String getCron(String date) throws ParseException {
return formatDateByPattern(date);
}
}
到这里讲一下,其实我本来不想用SimpleDateFormatter来将日期转换成Cron表达式(最开始想用DateTimeFormatter来实现),但是没实现成功。所以就用SimpleDateTimeFormatter来吧(这个类是线程不安全的,所以在使用时一定要注意,要么像我那样用ThreadLocal用共享对象,要么就用sychronized来锁定范围)
最后,在使用时还有可能会有问题(如果你项目中有websocket)的话。问题和解决方案如下:
https://www.cnblogs.com/threadj/articles/10631193.html
最后的最后,本篇文章只是浅显的实现,具体的内部调用可以看如下文章:
https://my.oschina.net/u/3773302/blog/4704918