美文网首页
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