zoukankan      html  css  js  c++  java
  • SpringBoot设置定时任务

    SpringBoot定时任务实现方式有多种

    1.单线程定时任务

    a: 启动类上加注解@EnableScheduling // 开启对定时任务的支持

    b: 在方法上使用注解@Scheduled(cron = "10 * * * * ?")来设置任务执行时间
    package com.example.timetask.common.task;
    
    import com.example.timetask.util.DateUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    /**
     * 使同一个线程中串行执行
     */
    @Slf4j
    @Component
    public class ScheduledService {
        @Scheduled(cron = "10 * * * * ?")
        public void scheduled(){
            log.info("定时任务1 >>>>> 线程名称:  {} ,时间:   {}",Thread.currentThread().getName(), DateUtil.getCurrentDateTime());
            try {
                // 模拟定时任务执行10s
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Scheduled(cron = "12 * * * * *")
        public void scheduled2(){
            log.info("定时任务2 >>>>> 线程名称:  {} ,时间:   {}",Thread.currentThread().getName(), DateUtil.getCurrentDateTime());
        }
    }

    上面代码中定义了两个定时任务,

    定时任务一10秒的时候执行并停顿10秒模拟任务执行

    定时任务二12秒的时候执行

    启动程序输出如下:

    2021-01-12 14:00:10.003  INFO 14100 --- [scheduling-1] c.e.t.common.task.ScheduledService :定时任务1 >>>>> 线程名称: scheduling-1 ,时间: 2021-01-12 14:00:10
    2021-01-12 14:00:20.004  INFO 14100 --- [scheduling-1] c.e.t.common.task.ScheduledService :定时任务2 >>>>> 线程名称: scheduling-1 ,时间: 2021-01-12 14:00:20

    从输出结果可以看出,定时任务一和定时任务二都是同一个线程scheduling-1执行的,并且定时任务二在12秒的时候并没有执行,而是等到定时任务一执行完成之后才开始执行

    优点:

      开启定时任务简单,只需一个注解

    缺点:

      定时任务是单线程执行的,多个任务同时执行时后面的任务会阻塞

      定时任务执行时间是写死在代码中的,后期修改定时任务时间麻烦

    2.多线程定时任务

      a: 启动类上加上注解  @EnableScheduling // 开启对定时任务的支持 和  @EnableAsync // 开启异步执行

      b: 在方法上加上注解 

        @Scheduled(cron = "10 * * * * *") //定时任务执行时间 

        @Async // 异步执行

    package com.example.timetask.common.task;
    
    import com.example.timetask.util.DateUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    /**
     * 异步执行
     */
    @Slf4j
    @Component
    public class ScheduledService {
        @Async
        @Scheduled(cron = "10 * * * * *")
        public void asyncScheduled1(){
            log.info("异步定时任务2 >>>>> 线程名称:  {} ,时间:   {}",Thread.currentThread().getName(), DateUtil.getCurrentDateTime());
            try {
                // 模拟定时任务执行10s
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Async
        @Scheduled(cron = "12 * * * * *")
        public void asyncScheduled2() {
            log.info("异步定时任务2 >>>>> 线程名称:  {} ,时间:   {}",Thread.currentThread().getName(), DateUtil.getCurrentDateTime());
        }
    }

    同样的设置任务一在10s的时候执行,任务执行10s,设置任务二在12s的时候执行

    启动程序输出如下

    2021-01-12 15:06:10.008  INFO 5660 --- [ task-1] c.e.t.common.task.ScheduledService : 异步定时任务2 >>>>> 线程名称:  task-1 ,时间:2021-01-12 15:06:10
    2021-01-12 15:06:12.003  INFO 5660 --- [ task-2] c.e.t.common.task.ScheduledService : 异步定时任务2 >>>>> 线程名称:  task-2 ,时间:2021-01-12 15:06:12

    从日志看出 两个任务使用了不同的线程来执行的,

    定时任务一在10s的时候执行了,定时任务二在12s的时候执行了,任务一并没有影响任务二的执行

    优点:

      多线程执行

    缺点:

      定时任务时间写死在代码中不方便修改

    3.实现SchedulingConfigurer接口实现可配置的定时任务

      a: 启动类上加注解@EnableScheduling // 开启对定时任务的支持

      b: 实现SchedulingConfigurer接口,添加定时任务

    package com.example.timetask.common.task;
    
    import com.example.timetask.util.DateUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.SchedulingConfigurer;
    import org.springframework.scheduling.config.ScheduledTaskRegistrar;
    
    import java.util.concurrent.Executors;
    
    @Configuration
    @Slf4j
    public class ScheduledConfig implements SchedulingConfigurer {
        public static final String taskCron="10 * * * * ?";
        public static final String taskCron2="12 * * * * ?";
    
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
            // 1.重新自定义定时任务线程池
            taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
    
            taskRegistrar.addCronTask(()->{
                log.info("异步定时任务1 >>>>> 线程名称:  {} ,时间:   {}",Thread.currentThread().getName(), DateUtil.getCurrentDateTime());
                try {
                    // 模拟定时任务执行10s
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },taskCron);
    
            taskRegistrar.addCronTask(()->{
                log.info("异步定时任务2 >>>>> 线程名称:  {} ,时间:   {}",Thread.currentThread().getName(), DateUtil.getCurrentDateTime());
            },taskCron2);
    
        }
    }

    还是一样添加了两个定时任务

    启动后输出结果如下

    2021-01-12 15:30:10.001 INFO 4636 --- [pool-1-thread-2] c.e.t.common.task.ScheduledConfig : 异步定时任务1 >>>>> 线程名称: pool-1-thread-2 ,时间: 2021-01-12 15:30:10
    2021-01-12 15:30:12.001 INFO 4636 --- [pool-1-thread-1] c.e.t.common.task.ScheduledConfig : 异步定时任务2 >>>>> 线程名称: pool-1-thread-1 ,时间: 2021-01-12 15:30:12

    和方法2的结果是一样,多线程执行,

    优点:

      多线程执行

      定时时间可写在配置文件中

    缺点:

      实现了定时时间的可配置但是还是不够方便,如果定时任务修改还得重启项目才能生效

    4.在3的基础上把定时任务配置信息写到数据库中,通过反射的方式去执行,并实现在前台可控制的界面便于修改定时任务执行时间,和启停定时任务

    a.创建一个定时任务实体类

    @Data
    public class TaskEntity {
        /**主键id*/
        private Integer id;
        /**任务名称*/
        private String taskName;
        /**全路径类名*/
        private String className;
        /**方法名*/
        private String methodName;
        /**cron表达式*/
        private String cron;
        /**定时任务状态 0:停止  1:启动*/
        private String state;
        /**备注*/
        private String remarks;
        /**创建时间*/
        private String createTime;
        /**更新时间*/
        private String updateTime;
    }

    b,数据库中创建如下表来存储任务

    CREATE TABLE `t_task`  (
      `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
      `taskName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '任务名称',
      `className` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '全路径类名',
      `methodName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '方法名',
      `cron` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'cron表达式',
      `state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '定时任务状态',
      `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
      `createTime` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
      `updateTime` datetime(0) NULL DEFAULT NULL COMMENT '最后更新时间',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

    c,定时任务配置类

    @Configuration
    @Slf4j
    public class DynamicScheduled implements SchedulingConfigurer {
    
        private static ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    
        private static Map<String, ScheduledFuture<?>> scheduledFutureMap = new HashMap<>();
    
        static {
            threadPoolTaskScheduler.initialize();
        }
    
        @Autowired
        private TaskService taskService;/**
         * @param taskRegistrar
         */
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
            // 1.重新自定义定时任务线程池
            taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
    
            // 2.从数据库中查出开启状态的定时任务
            TaskEntity condition = new TaskEntity();
            condition.setState("1");
            List<TaskEntity> data = taskService.getAll(condition);
    
            // 3.循环启动定时人任务
            for (TaskEntity taskEntity : data) {
                // 4.启动定时任务
                start(taskEntity);
            }
        }
    
        /**
         * 启动定时任务
         * @param taskEntity
         * @param
         */
        public static void start(TaskEntity taskEntity){
            ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(new Runnable() {
                @Override
                public void run() {
                    String className = taskEntity.getClassName();
                    String methodName = taskEntity.getMethodName();
                    try {
                        Class<?> aClass = Class.forName(className);
                        Method method = aClass.getMethod(methodName);
                        Object o = aClass.newInstance();
                        method.invoke(o);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
    
                }
            }, new Trigger() {
                @Override
                public Date nextExecutionTime(TriggerContext triggerContext) {
                    CronTrigger cronTrigger = new CronTrigger(taskEntity.getCron());
                    Date date = cronTrigger.nextExecutionTime(triggerContext);
                    return date;
                }
            });
            scheduledFutureMap.put(taskEntity.getId().toString(),scheduledFuture);
            log.info("启动定时任务" + taskEntity.getId() );
    
        }
    
        /**
         * 取消定时任务
         * @param task
         */
        public static void cancel(TaskEntity task){
            ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(task.getId().toString());
            if(scheduledFuture != null && !scheduledFuture.isCancelled()){
                scheduledFuture.cancel(Boolean.FALSE);
            }
            scheduledFutureMap.remove(task.getId());
            log.info("取消定时任务" + task.getId() );
        }
    
        /**
         * 编辑
         * @param task
         * @param
         */
        public static void reset(TaskEntity task){
            log.info("修改定时任务开始" + task.getId() );
            cancel(task);
            start(task);
            log.info("修改定时任务结束" + task.getId());
        }
    
        /**
         * 校验cron表达式是否正确
         * @param cron
         * @return
         */
        public static boolean checkCronExpression(String cron){
            try {
                CronTrigger cronTrigger = new CronTrigger(cron);
            } catch (Exception e) {
                log.error("cron表达式错误: {}",cron);
                return false;
            }
            return true;
        }
    }

    d.编写一个定时任务增删改查的页面,在页面增删改定时任务时调用上面的方法来做相应的处理,最终实现效果如下

     

    具体代码请看https://gitee.com/chengzhongyi/timed-task

    梦 想 不 大 , 道 路 很 长 , 开 始 了 就 别 停 下
  • 相关阅读:
    [转载]小谈网络游戏同步
    [ASE][Daily Scrum]11.06
    [Proposal]Tank Battle——Infinite
    [proposal][app]Watch your time!
    [Proposal]Nano-Diary(纳日记)
    LaTeX中用BibTex管理参考文献
    matlab化简符号表达式
    placeholder颜色变化
    链接图片外边出现蓝框(IE8/IE9/IE10)
    图标排列
  • 原文地址:https://www.cnblogs.com/chengzhongyi/p/10441505.html
Copyright © 2011-2022 走看看