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

 

posted @ 2019-02-27 09:26  露天窗  阅读(534)  评论(0编辑  收藏  举报