写在前面
SpringBoot创建定时任务的方式很简单,主要有两种方式:一、基于注解的方式(@Scheduled)二、数据库动态配置。实际开发中,第一种需要在代码中写死表达式,如果修改起来,又得重启会显得很麻烦;所以我们往往会采取第二种方式,可以直接从数据库中读取定时任务的指定执行时间,无需重启。
下面就来介绍下这两种方式吧
一、基于注解(@Scheduled)
基于注解是一种静态的方式,只需要几行代码就可以搞定了
添加一个配置类
@Configuration //标记配置类 @EnableScheduling //开启定时任务 public class MyScheduleConfig { //添加定时任务 @Scheduled(cron = "0/5 * * * * ?") private void myTasks() { System.out.println("执行定时任务 " + LocalDateTime.now()); } }
上面代码的cron表达式表示每5秒执行一次,可以通过这个网站(http://tools.jb51.net/code/Quartz_Cron_create)去生成要的cron表达式
启动应用,控制台看效果
这个方式的确很简单方便,但前面介绍也说到了,有个缺点就是当我们需要去修改定时任务的执行周期或者停止的时候,我们需要到代码层去修改,重启。
二、数据库动态配置
这里使用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根据是完整的类名
1.3 引入依赖
org.springframework.boot spring-boot-starter-web mysql mysql-connector-java 5.1.49 runtime com.baomidou mybatis-plus-boot-starter 3.3.1.tmp org.projectlombok lombok 1.18.20 provided
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.Driver server: servlet: context-path: /demo port: 8888
2、疯狂贴代码
2.1 创建定时任务线程池
@Configuration @Slf4j public class ScheduledConfig { @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { log.info("创建定时任务调度线程池 start"); ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(20); threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-"); threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true); threadPoolTaskScheduler.setAwaitTerminationSeconds(60); log.info("创建定时任务调度线程池 end"); return threadPoolTaskScheduler; } }
2.2 项目启动时初始化定时任务
@Slf4j @Component public class ScheduledTaskRunner implements ApplicationRunner { @Autowired private ScheduledTaskService scheduledTaskService; @Override public void run(ApplicationArguments args) throws Exception { log.info("----初始化定时任务开始----"); scheduledTaskService.initTask(); log.info("----初始化定时任务完成----"); } }
2.3 定时任务公共接口
public interface ScheduledOfTask extends Runnable{ void execute(); @Override default void run() { execute(); } }
2.4 创建两个定时任务实现类
@Component @Slf4j public class TaskJob1 implements ScheduledOfTask{ @Override public void execute() { log.info("执行任务1 "+ LocalDateTime.now()); } }
@Component @Slf4j public class TaskJob2 implements ScheduledOfTask{ @Override public void execute() { log.info("执行任务2 "+ LocalDateTime.now()); } }
2.5 定时任务管理接口
public interface ScheduledTaskService{ Boolean start(ScheduledJob scheduledJob); Boolean stop(String jobKey); Boolean restart(ScheduledJob scheduledJob); void initTask(); }
2.6 定时任务管理实现类
@Slf4j @Service public class ScheduledTaskServiceImpl implements ScheduledTaskService { /** * 可重入锁 */ private ReentrantLock lock = new ReentrantLock(); /** * 定时任务线程池 */ @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; /** * 启动状态的定时任务集合 */ public MapscheduledFutureMap = new ConcurrentHashMap<>(); @Autowired private ScheduledJobService scheduledJobService; @Override public Boolean start(ScheduledJob scheduledJob) { String jobKey = scheduledJob.getJobKey(); log.info("启动定时任务"+jobKey); //添加锁放一个线程启动,防止多人启动多次 lock.lock(); log.info("加锁完成"); try { if(this.isStart(jobKey)){ log.info("当前任务在启动状态中"); return false; } //任务启动 this.doStartTask(scheduledJob); } finally { lock.unlock(); log.info("解锁完毕"); } return true; } /** * 任务是否已经启动 */ private Boolean isStart(String taskKey) { //校验是否已经启动 if (scheduledFutureMap.containsKey(taskKey)) { if (!scheduledFutureMap.get(taskKey).isCancelled()) { return true; } } return false; } @Override public Boolean stop(String jobKey) { log.info("停止任务 "+jobKey); boolean flag = scheduledFutureMap.containsKey(jobKey); log.info("当前实例是否存在 "+flag); if(flag){ ScheduledFuture scheduledFuture = scheduledFutureMap.get(jobKey); scheduledFuture.cancel(true); scheduledFutureMap.remove(jobKey); } return flag; } @Override public Boolean restart(ScheduledJob scheduledJob) { log.info("重启定时任务"+scheduledJob.getJobKey()); //停止 this.stop(scheduledJob.getJobKey()); return this.start(scheduledJob); } /** * 执行启动任务 */ public void doStartTask(ScheduledJob sj){ log.info(sj.getJobKey()); if(sj.getStatus().intValue() != 1) return; Class clazz; ScheduledOfTask task; try { clazz = Class.forName(sj.getJobKey()); task = (ScheduledOfTask) SpringContextUtil.getBean(clazz); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("spring_scheduled_cron表数据" + sj.getJobKey() + "有误", e); } Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口"); ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(task,(triggerContext -> new CronTrigger(sj.getCronExpression()).nextExecutionTime(triggerContext))); scheduledFutureMap.put(sj.getJobKey(),scheduledFuture); } @Override public void initTask() { List list = scheduledJobService.list(); for (ScheduledJob sj : list) { if(sj.getStatus().intValue() == -1) //未启用 continue; doStartTask(sj); } } }
2.8 上面用到的获取Bean的工具类SpringContextUtil
@Component public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext = null; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if(SpringContextUtil.applicationContext == null){ SpringContextUtil.applicationContext = applicationContext; } } public static ApplicationContext getApplicationContext() { return applicationContext; } public static Object getBean(String name){ return getApplicationContext().getBean(name); } public staticT getBean(Class clazz){ return getApplicationContext().getBean(clazz); } public static T getBean(String name,Class clazz){ return getApplicationContext().getBean(name, clazz); } }
2.9 表操作对应的一些类
Pojo
@Data @TableName("scheduled_job") public class ScheduledJob { @TableId(value = "job_id",type = IdType.AUTO) private Integer jobId; private String jobKey; private String cronExpression; private String taskExplain; private Integer status; }
ScheduledJobMapper
public interface ScheduledJobMapper extends BaseMapper{ }
ScheduledJobService
public interface ScheduledJobService extends IService{ /** * 修改定时任务,并重新启动 * @param scheduledJob * @return */ boolean updateOne(ScheduledJob scheduledJob); }
@Service @Slf4j public class ScheduledJobServiceImpl extends ServiceImplimplements ScheduledJobService{ @Autowired private ScheduledTaskService scheduledTaskService; @Override public boolean updateOne(ScheduledJob scheduledJob) { if(updateById(scheduledJob)) scheduledTaskService.restart(getById(scheduledJob.getJobId())); return true; } }
2.10 修改定时任务的接口
@RestController @RequestMapping("/job") public class ScheduledJobController { @Autowired private ScheduledJobService scheduledJobService; @PostMapping(value = "/update") public CallBackResult update(HttpServletRequest request, ScheduledJob scheduledJob){ if(scheduledJobService.updateOne(scheduledJob)) return new CallBackResult(true,"修改成功"); return new CallBackResult(false,"修改失败"); } }
3、测试结果
3.1 启动项目,看下定时任务的执行结果,控制台输出结果
我们可以看到任务1是每5秒执行一次,任务2是12秒执行一次
3.2 修改任务1的cron参数或者状态
3.2.1 修改cron,执行周期改为20秒执行一次,状态不变
再看控制台输出结果,任务2没变化,任务1由5秒一次变成了20秒一次了
3.2.1 修改状态
再看控制台输出结果,任务2没变化,任务1已经不再执行了
最后
第二种方式支持通过接口的方式去改动,并且不需要重启,当然啦,也可以直接在数据库中添加或修改数据后重启项目,配置更加灵活一点。
如果是一个固定的需求,执行周期一定不会变的了,推荐还是第一种写法,毕竟简单嘛。
如果觉得写得还不错的话,给个推荐鼓励一下吧。
到此这篇关于SpringBoot如何实现定时任务的文章就介绍到这了,更多相关SpringBoot实现定时任务内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://www.cnblogs.com/jae-tech/p/15402078.html