1、定时任务创建
springboot内置了定时任务模块,可以通过在启动类上添加@EnableScheduling
开启定时任务功能。在定时任务方法上使用@Schedule
注解标记定时任务并配置定时任务执行时机。配置方法包括如下三种:
- 固定延迟时间:
// 初次调用延迟3秒,任务结束后5秒钟再次调用,两次调用不会同步运行
@Scheduled(initialDelay = 3000, fixedDelay = 5000)
public void timerTask() {
try {
System.out.println(System.currentTimeMillis() / 1000);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 固定间隔时间:
// 初次调用延迟3秒,每隔5秒钟调用一次,多次调用可能同步运行
@Scheduled(initialDelay = 3000, fixedRate = 5000)
public void timerTask() {
System.out.println(System.currentTimeMillis() / 1000);
}
- 通过cron表达式配置:
// 从第0秒开始每10秒钟执行一次
@Scheduled(cron = "0/10 * * * * ?")
public void timerTask() {
System.out.println(System.currentTimeMillis() / 1000);
}
// 从配置文件中读取cron表达式
@Scheduled(cron = "${task.cron}")
public void timerTask() {
System.out.println(System.currentTimeMillis() / 1000);
}
2、Cron表达式详解
Cron表达式是一个以5或6个空格隔开的字符串,分为6或7个域,从左到右分别为秒 分 时 月份中的日期 月份 星期中的日期 年份?
:
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒(Seconds) | 0~59的整数 | , - * / |
分(Minutes) | 0~59的整数 | , - * / |
小时(Hours) | 0~23的整数 | , - * / |
日期(DayofMonth) | 1~31的整数(但是你需要考虑你月的天数) | ,- * ? / L W C |
月份(Month) | 1~12的整数或者 JAN-DEC | , - * / |
星期(DayofWeek) | 1~7的整数或者 SUN-SAT (1=SUN) | , - * ? / L C # |
年(可选,留空)(Year) | 1970~2099 | , - * / |
每一个域都使用数字,但还可以出现特殊字符,它们的含义如下:
-
,
:表示列出枚举值。如在Minutes域使用5,20,表示在第5分和第20分进行触发; -
-
:表示范围。如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次; -
*
:表示匹配该域的任意值。如在Minutes域使用*,即表示每分钟都会触发事件; -
/
:表示间隔时间触发。如在Minutes域使用5/20,表示从第5分钟其每20分钟触发一次; -
?
:仅用在DayofMonth和DayofWeek两个关联域,表示不确定的值,此时日由另一个域控制; -
L
:仅用在DayofMonth和DayofWeek两个域,表示月的倒数第几天或最后一个星期几; -
W
:仅用在DayofMonth域,表示不跨月份的最近一个工作日,还可以和L连用表示月的最后一个工作日; -
#
:仅用在DayofMonth域,用于确定每个月第几个星期几。如4#2,表示某月的第二个星期三。
3、动态修改定时任务配置
可以通过实现SchedulingConfigurer
接口来配置定时任务,实现不用重启服务修改定时器的cron参数:
@Component
public class DynamicTask implements SchedulingConfigurer {
private CronTrigger trigger = new CronTrigger("5/10 * * * * ?");
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(new Runnable() {
@Override
public void run() {
System.out.println("来自定时器:" + System.currentTimeMillis() / 1000);
}
}, new Trigger() {
@Nullable
@Override
public Date nextExecutionTime(TriggerContext context) {
if (trigger != null) {
return trigger.nextExecutionTime(context);
}
return null;
}
});
}
public void setCron(String cron) {
try {
this.trigger = new CronTrigger(cron);
} catch (Exception e) {
e.printStackTrace();
}
}
}
但是上述方法在修改完cron表达式之后,只有再一次到达定时任务的时间,才会调用新的触发时间,导致页面设置的时间不能即时生效,以下改进方法可修正此问题:
@Service
public class ScheduleManager implements SchedulingConfigurer {
private final Log logger = LogFactory.getLog(getClass());
/**
* 受ScheduleManager管理的任务集合
*/
private final Map<Object, ScheduledTask> taskMap = new HashMap<>();
/**
* 定时任务注册器
*/
private ScheduledTaskRegistrar taskRegistrar;
/**
* 系统启动时获取任务注册对象
*
* @param taskRegistrar 任务注册对象
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
logger.info("scheduleManager has successfully acquired the taskRegistrar");
}
/**
* 添加新任务,如果存在key一致的任务,则取消原任务的执行,但添加新任务失败时原任务不会停止
*
* @param key 任务key
* @param runnable 新任务执行代码
* @param cron 新任务cron表达式
*/
public void addTask(Object key, Runnable runnable, String cron) {
if (runnable != null && !StringUtils.isEmpty(cron)) {
ScheduledTask oldTask = taskMap.get(key);
taskMap.put(key, taskRegistrar.scheduleCronTask(new CronTask(runnable, cron)));
if (oldTask != null) {
oldTask.cancel();
}
}
}
/**
* 重置任务的执行时机,修改失败时任务仍使用原有执行时机
*
* @param key 任务key
* @param cron 任务新cron表达式
*/
public void modifyTask(Object key, String cron) {
ScheduledTask oldTask = taskMap.get(key);
if (oldTask != null && !StringUtils.isEmpty(cron)) {
taskMap.put(key, taskRegistrar.scheduleCronTask(new CronTask(oldTask.getTask().getRunnable(), cron)));
oldTask.cancel();
}
}
/**
* 取消任务执行
*
* @param key 任务key
*/
public void cancelTask(Object key) {
ScheduledTask task = taskMap.remove(key);
if (task != null) {
task.cancel();
}
}
/**
* 查看是否存在任务
*
* @param key 任务key
* @return 如果任务存在返回true,否则返回false
*/
public boolean existTask(Object key) {
return taskMap.get(key) != null;
}
}
使用的SpringBoot版本为2.0.0.RELEASE
,点此查看部分代码。
4、实际项目中定时任务的配置
实际项目中,为提高服务响应能力和实现负载均衡,我们一般会通过微服务或反向代理的方法多实例部署项目。如果将定时任务写项目中,就可能导致任务重复执行。可以在配置文件中增加自定义配置,在我们的实际代码中进行判断,是否执行真正的处理逻辑;也可以将真正要定时任务处理的逻辑,写成rest或rpc服务,然后通过定时任务调用。如果SpringBoot内置的定时任务无法满足需求,还可以通过集成Quartz等第三方框架实现更复杂的定时任务功能。
网友评论