美文网首页
SpringBoot Scheduled定时任务学习及使用

SpringBoot Scheduled定时任务学习及使用

作者: wyon | 来源:发表于2018-03-21 17:35 被阅读0次

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等第三方框架实现更复杂的定时任务功能。

参考

相关文章

网友评论

      本文标题:SpringBoot Scheduled定时任务学习及使用

      本文链接:https://www.haomeiwen.com/subject/zuubqftx.html