基于注解(@Scheduled)
- 配置类
@Configuration //标记配置类 @EnableScheduling //开启定时任务 public class MyScheduleConfig { //添加定时任务 @Scheduled(cron = "0/5 * * * * ?") // 每5秒执行一次 private void myTasks() { System.out.println("执行定时任务 " + LocalDateTime.now()); } }
查看cron表达式
- 启动应用,控制台看效果
- 简单方便
- 缺点:修改定时任务的执行周期或者停止时,需要修改代码,重启。
数据库动态配置
- 添加MySQL表
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='定时任务表';
插入两条数据,job_key是完整的类名
- 创建定时任务线程池
@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; } }
- 项目启动时初始化定时任务
@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("----初始化定时任务完成----"); } }
- 定时任务公共接口
public interface ScheduledOfTask extends Runnable{ void execute(); @Override default void run() { execute(); } }
- 创建两个定时任务实现类
@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()); } }
- 定时任务管理接口
public interface ScheduledTaskService{ Boolean start(ScheduledJob scheduledJob); Boolean stop(String jobKey); Boolean restart(ScheduledJob scheduledJob); void initTask(); }
- 定时任务管理实现类
@Slf4j @Service public class ScheduledTaskServiceImpl implements ScheduledTaskService { /** * 可重入锁 */ private ReentrantLock lock = new ReentrantLock(); /** * 定时任务线程池 */ @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; /** * 启动状态的定时任务集合 */ public Map scheduledFutureMap = 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); } } }
- 获取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 static T getBean(Class clazz){ return getApplicationContext().getBean(clazz); } public static T getBean(String name,Class clazz){ return getApplicationContext().getBean(name, clazz); } }
- 表操作
@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; }
public interface ScheduledJobMapper extends BaseMapper { }
public interface ScheduledJobService extends IService { /** * 修改定时任务,并重新启动 * @param scheduledJob * @return */ boolean updateOne(ScheduledJob scheduledJob); }
@Service @Slf4j public class ScheduledJobServiceImpl extends ServiceImpl implements ScheduledJobService{ @Autowired private ScheduledTaskService scheduledTaskService; @Override public boolean updateOne(ScheduledJob scheduledJob) { if(updateById(scheduledJob)) scheduledTaskService.restart(getById(scheduledJob.getJobId())); return true; } }
- 修改定时任务的接口
@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,"修改失败"); } }
可以看到任务1是每5秒执行一次,任务2是12秒执行一次
- 修改任务1的cron参数或者状态
修改cron,执行周期改为20秒执行一次,状态不变
再看控制台输出结果,任务2没变化,任务1由5秒一次变成了20秒一次了
修改状态
再看控制台输出结果,任务2没变化,任务1已经不再执行了
支持通过接口的方式去改动,并且不需要重启