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