SpringBoot如何实现定时任务(springboot中如何实现定时任务)

写在前面

SpringBoot创建定时任务的方式很简单,主要有两种方式:一、基于注解的方式(@Scheduled)二、数据库动态配置。实际开发中,第一种需要在代码中写死表达式,如果修改起来,又得重启会显示很麻烦;所以我们往往会采取第二种方式,可以直接从数据库中读取定时任务的指定执行时间,无需重启。

下面就来介绍下这两种方式吧

一、基于注解(@Scheduled)

基于注解是一种静态的方式,只需要几行代码就可以搞定了

添加一个配置类

1 @Configuration //标记配置类 2 @EnableScheduling //开启定时任务 3 public class MyScheduleConfig { 4 5 //添加定时任务 6 @Scheduled(cron = "0/5 * * * * ?") 7 private void myTasks() { 8 System.out.println("执行定时任务 " LocalDateTime.now()); 9 }10 }

上面代码的cron表达式表示每5秒执行一次,可以通过这个网站(https://qqe2.com/cron)去生成要的cron表达式

启动应用,控制台看效果

SpringBoot如何实现定时任务(springboot中如何实现定时任务)

这个方式的确很简单方便,但前面介绍也说到了,有个缺点就是当我们需要去修改定时任务的执行周期或者停止的时候,我们需要到代码层去修改,重启。

二、数据库动态配置

这里使用MySQL数据库

1、表数据添加,资源配置

1.1 添加表

CREATE TABLE `scheduled_job` ( `job_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', `job_key` varchar(128) NOT NULL COMMENT '定时任务完整类名', `cron_expression` varchar(20) NOT NULL COMMENT 'cron表达式', `task_explain` varchar(50) NOT NULL DEFAULT '' COMMENT '任务描述', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:正常;-1:停用', PRIMARY KEY (`job_id`), UNIQUE KEY `job_key` (`job_key`), UNIQUE KEY `cron_key_unique_idx` (`job_key`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='定时任务表';

1.2 插入两条数据,job_key根据是完整的类名

SpringBoot如何实现定时任务(springboot中如何实现定时任务)

1.3 引入依赖

<dependency> <groupId>org.Springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> <scope>runtime</scope> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency> <!--lombok简化代码--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>provided</scope> </dependency>

1.4 配置application.yml

spring: datasource: url: jdbc:mysql://127.0.0.1:3306/test?userUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123 driver-class-name: com.mysql.jdbc.Driverserver: servlet: context-path: /demo port: 8888

2、疯狂贴代码

2.1 创建定时任务线程

1 @Configuration 2 @Slf4j 3 public class ScheduledConfig { 4 5 @Bean 6 public ThreadPoolTaskScheduler threadPoolTaskScheduler() { 7 log.info("创建定时任务调度线程池 start"); 8 ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); 9 threadPoolTaskScheduler.setPoolSize(20);10 threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");11 threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);12 threadPoolTaskScheduler.setAwaitTerminationSeconds(60);13 log.info("创建定时任务调度线程池 end");14 return threadPoolTaskScheduler;15 }16 17 }

2.2 项目启动时初始化定时任务

1 @Slf4j 2 @Component 3 public class ScheduledTaskRunner implements ApplicationRunner { 4 @Autowired 5 private ScheduledTaskService scheduledTaskService; 6 7 @Override 8 public void run(ApplicationArguments args) throws Exception { 9 log.info("----初始化定时任务开始----");10 scheduledTaskService.initTask();11 log.info("----初始化定时任务完成----");12 }13 }

2.3 定时任务公共接口

1 public interface ScheduledOfTask extends Runnable{2 3 void execute();4 5 @Override6 default void run() {7 execute();8 }9 }

2.4 创建两个定时任务实现类

1 @Component2 @Slf4j3 public class TaskJob1 implements ScheduledOfTask{4 @Override5 public void execute() {6 log.info("执行任务1 " LocalDateTime.now());7 }8 }

1 @Component2 @Slf4j3 public class TaskJob2 implements ScheduledOfTask{4 @Override5 public void execute() {6 log.info("执行任务2 " LocalDateTime.now());7 }8 }

2.5 定时任务管理接口

1 public interface ScheduledTaskService{ 2 3 Boolean start(ScheduledJob scheduledJob); 4 5 Boolean stop(String jobKey); 6 7 Boolean Restart(ScheduledJob scheduledJob); 8 9 void initTask();10 }

2.6 定时任务管理实现类

1 @Slf4j 2 @Service 3 public class ScheduledTaskServiceImpl implements ScheduledTaskService { 4 5 /** 6 * 可重入锁 7 */ 8 private ReentrantLock lock = new ReentrantLock(); 9 10 /** 11 * 定时任务线程池 12 */ 13 @Autowired 14 private ThreadPoolTaskScheduler threadPoolTaskScheduler; 15 16 /** 17 * 启动状态的定时任务集合 18 */ 19 public Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<>(); 20 21 @Autowired 22 private ScheduledJobService scheduledJobService; 23 24 @Override 25 public Boolean start(ScheduledJob scheduledJob) { 26 String jobKey = scheduledJob.getJobKey(); 27 log.info("启动定时任务" jobKey); 28 //添加锁放一个线程启动,防止多人启动多次 29 lock.lock(); 30 log.info("加锁完成"); 31 32 try { 33 if(this.isStart(jobKey)){ 34 log.info("当前任务在启动状态中"); 35 return false; 36 } 37 //任务启动 38 this.doStartTask(scheduledJob); 39 } finally { 40 lock.unlock(); 41 log.info("解锁完毕"); 42 } 43 44 return true; 45 } 46 47 /** 48 * 任务是否已经启动 49 */ 50 private Boolean isStart(String taskKey) { 51 //校验是否已经启动 52 if (scheduledFutureMap.containsKey(taskKey)) { 53 if (!scheduledFutureMap.get(taskKey).isCancelled()) { 54 return true; 55 } 56 } 57 return false; 58 } 59 60 @Override 61 public Boolean stop(String jobKey) { 62 log.info("停止任务 " jobKey); 63 boolean flag = scheduledFutureMap.containsKey(jobKey); 64 log.info("当前实例是否存在 " flag); 65 if(flag){ 66 ScheduledFuture scheduledFuture = scheduledFutureMap.get(jobKey); 67 68 scheduledFuture.cancel(true); 69 70 scheduledFutureMap.remove(jobKey); 71 } 72 return flag; 73 } 74 75 @Override 76 public Boolean restart(ScheduledJob scheduledJob) { 77 log.info("重启定时任务" scheduledJob.getJobKey()); 78 //停止 79 this.stop(scheduledJob.getJobKey()); 80 81 return this.start(scheduledJob); 82 } 83 84 /** 85 * 执行启动任务 86 */ 87 public void doStartTask(ScheduledJob sj){ 88 log.info(sj.getJobKey()); 89 if(sj.getStatus().intValue() != 1) 90 return; 91 Class<?> clazz; 92 ScheduledOfTask task; 93 try { 94 clazz = Class.forName(sj.getJobKey()); 95 task = (ScheduledOfTask) SpringContextUtil.getBean(clazz); 96 } catch (ClassNotFoundException e) { 97 throw new IllegalArgumentException("spring_scheduled_cron表数据" sj.getJobKey() "有误", e); 98 } 99 Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");100 ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(task,(triggerContext -> new CronTrigger(sj.getCronExpression()).nextExecutionTime(triggerContext)));101 scheduledFutureMap.put(sj.getJobKey(),scheduledFuture);102 }103 104 @Override105 public void initTask() {106 List<ScheduledJob> list = scheduledJobService.list();107 for (ScheduledJob sj : list) {108 if(sj.getStatus().intValue() == -1) //未启用109 continue;110 doStartTask(sj);111 }112 }113 }

2.8 上面用到的获取Bean的工具类SpringContextUtil

1 @Component 2 public class SpringContextUtil implements ApplicationContextAware { 3 4 5 private static ApplicationContext applicationContext = null; 6 @Override 7 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 8 if(SpringContextUtil.applicationContext == null){ 9 SpringContextUtil.applicationContext = applicationContext;10 }11 }12 13 public static ApplicationContext getApplicationContext() {14 return applicationContext;15 }16 17 public static Object getBean(String name){18 return getApplicationContext().getBean(name);19 }20 21 public static <T> T getBean(Class<T> clazz){22 return getApplicationContext().getBean(clazz);23 }24 25 public static <T> T getBean(String name,Class<T> clazz){26 return getApplicationContext().getBean(name, clazz);27 }28 }

2.9 表操作对应的一些类

Pojo

1 @Data 2 @TableName("scheduled_job") 3 public class ScheduledJob { 4 5 @TableId(value = "job_id",type = IdType.AUTO) 6 private Integer jobId; 7 8 private String jobKey; 9 10 private String cronExpression;11 12 private String taskExplain;13 14 private Integer status;15 16 }

ScheduledJobMapper

1 public interface ScheduledJobMapper extends BaseMapper<ScheduledJob> {2 }

ScheduledJobService

1 public interface ScheduledJobService extends IService<ScheduledJob> {2 3 /**4 * 修改定时任务,并重新启动5 * @param scheduledJob6 * @return7 */8 boolean updateOne(ScheduledJob scheduledJob);9 }

1 @Service 2 @Slf4j 3 public class ScheduledJobServiceImpl extends ServiceImpl<ScheduledJobMapper, ScheduledJob> implements ScheduledJobService{ 4 5 @Autowired 6 private ScheduledTaskService scheduledTaskService; 7 8 @Override 9 public boolean updateOne(ScheduledJob scheduledJob) {10 if(updateById(scheduledJob))11 scheduledTaskService.restart(getById(scheduledJob.getJobId()));12 return true;13 }14 }

2.10 修改定时任务的接口

1 @RestController 2 @RequestMapping("/job") 3 public class ScheduledJobController { 4 5 @Autowired 6 private ScheduledJobService scheduledJobService; 7 8 @PostMapping(value = "/update") 9 public CallBackResult update(HttpServletRequest request, ScheduledJob scheduledJob){10 if(scheduledJobService.updateOne(scheduledJob))11 return new CallBackResult(true,"修改成功");12 return new CallBackResult(false,"修改失败");13 }14 15 }

3、测试结果

3.1 启动项目,看下定时任务的执行结果,控制台输出结果

SpringBoot如何实现定时任务(springboot中如何实现定时任务)

我们可以看到任务1是每5秒执行一次,任务2是12秒执行一次

3.2 修改任务1的cron参数或者状态

3.2.1 修改cron,执行周期改为20秒执行一次,状态不变

SpringBoot如何实现定时任务(springboot中如何实现定时任务)SpringBoot如何实现定时任务(springboot中如何实现定时任务)

再看控制台输出结果,任务2没变化,任务1由5秒一次变成了20秒一次了

再看控制台输出结果,任务2没变化,任务1由5秒一次变成了20秒一次了

SpringBoot如何实现定时任务(springboot中如何实现定时任务)

3.2.1 修改状态

第二种方式支持通过接口的方式去改动,并且不需要重启,当然啦,也可以直接在数据库中添加或修改数据后重启项目,配置更加灵活一点。

如果是一个固定的需求,执行周期一定不会变的了,推荐还是第一种写法,毕竟简单嘛。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

(0)
上一篇 2022年6月10日 上午10:42
下一篇 2022年6月10日 上午10:44

相关推荐