在spring boot项目中,可以通过@EnableScheduling注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止任务。
要实现动态增删启停定时任务功能,比较广泛的做法是集成Quartz框架。但是本人的开发原则是:在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂。
查看spring-context这个jar包中org.springframework.scheduling.ScheduledTaskRegistrar这个类的源代码,发现可以通过改造这个类就能实现动态增删启停定时任务功能。
定时任务列表页
定时任务执行日志
添加执行定时任务的线程池配置类
@Configuration?
public class SchedulingConfig {?
? ? @Bean?
? ? public TaskScheduler taskScheduler() {?
? ? ? ? ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();?
? ? ? ? // 定时任务执行线程池核心线程数?
? ? ? ? taskScheduler.setPoolSize(4);?
? ? ? ? taskScheduler.setRemoveOnCancelPolicy(true);?
? ? ? ? taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");?
? ? ? ? return taskScheduler;?
? ? }?
}
添加ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。
public final class ScheduledTask {?
? ? volatile ScheduledFuture<?> future;?
? ? /**?
? ? * 取消定时任务?
? ? */?
? ? public void cancel() {?
? ? ? ? ScheduledFuture<?> future = this.future;?
? ? ? ? if (future != null) {?
? ? ? ? ? ? future.cancel(true);?
? ? ? ? }?
? ? }?
}
添加Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。
public class SchedulingRunnable implements Runnable {?
? ? private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);?
? ? private String beanName;?
? ? private String methodName;?
? ? private String params;?
? ? public SchedulingRunnable(String beanName, String methodName) {?
? ? ? ? this(beanName, methodName, null);?
? ? }?
? ? public SchedulingRunnable(String beanName, String methodName, String params) {?
? ? ? ? this.beanName = beanName;?
? ? ? ? this.methodName = methodName;?
? ? ? ? this.params = params;?
? ? }?
? ? @Override?
? ? public void run() {?
? ? ? ? logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);?
? ? ? ? long startTime = System.currentTimeMillis();?
? ? ? ? try {?
? ? ? ? ? ? Object target = SpringContextUtils.getBean(beanName);?
? ? ? ? ? ? Method method = null;?
? ? ? ? ? ? if (StringUtils.isNotEmpty(params)) {?
? ? ? ? ? ? ? ? method = target.getClass().getDeclaredMethod(methodName, String.class);?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? method = target.getClass().getDeclaredMethod(methodName);?
? ? ? ? ? ? }?
? ? ? ? ? ? ReflectionUtils.makeAccessible(method);?
? ? ? ? ? ? if (StringUtils.isNotEmpty(params)) {?
? ? ? ? ? ? ? ? method.invoke(target, params);?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? method.invoke(target);?
? ? ? ? ? ? }?
? ? ? ? } catch (Exception ex) {?
? ? ? ? ? ? logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);?
? ? ? ? }?
? ? ? ? long times = System.currentTimeMillis() - startTime;?
? ? ? ? logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);?
? ? }?
? ? @Override?
? ? public boolean equals(Object o) {?
? ? ? ? if (this == o) return true;?
? ? ? ? if (o == null || getClass() != o.getClass()) return false;?
? ? ? ? SchedulingRunnable that = (SchedulingRunnable) o;?
? ? ? ? if (params == null) {?
? ? ? ? ? ? return beanName.equals(that.beanName) &&?
? ? ? ? ? ? ? ? ? ? methodName.equals(that.methodName) &&?
? ? ? ? ? ? ? ? ? ? that.params == null;?
? ? ? ? }?
? ? ? ? return beanName.equals(that.beanName) &&?
? ? ? ? ? ? ? ? methodName.equals(that.methodName) &&?
? ? ? ? ? ? ? ? params.equals(that.params);?
? ? }?
? ? @Override?
? ? public int hashCode() {?
? ? ? ? if (params == null) {?
? ? ? ? ? ? return Objects.hash(beanName, methodName);?
? ? ? ? }?
? ? ? ? return Objects.hash(beanName, methodName, params);?
? ? }?
}
添加定时任务注册类,用来增加、删除定时任务。
@Component?
public class CronTaskRegistrar implements DisposableBean {?
? ? private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);?
? ? @Autowired?
? ? private TaskScheduler taskScheduler;?
? ? public TaskScheduler getScheduler() {?
? ? ? ? return this.taskScheduler;?
? ? }?
? ? public void addCronTask(Runnable task, String cronExpression) {?
? ? ? ? addCronTask(new CronTask(task, cronExpression));?
? ? }?
? ? public void addCronTask(CronTask cronTask) {?
? ? ? ? if (cronTask != null) {?
? ? ? ? ? ? Runnable task = cronTask.getRunnable();?
? ? ? ? ? ? if (this.scheduledTasks.containsKey(task)) {?
? ? ? ? ? ? ? ? removeCronTask(task);?
? ? ? ? ? ? }?
? ? ? ? ? ? this.scheduledTasks.put(task, scheduleCronTask(cronTask));?
? ? ? ? }?
? ? }?
? ? public void removeCronTask(Runnable task) {?
? ? ? ? ScheduledTask scheduledTask = this.scheduledTasks.remove(task);?
? ? ? ? if (scheduledTask != null)?
? ? ? ? ? ? scheduledTask.cancel();?
? ? }?
? ? public ScheduledTask scheduleCronTask(CronTask cronTask) {?
? ? ? ? ScheduledTask scheduledTask = new ScheduledTask();?
? ? ? ? scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());?
? ? ? ? return scheduledTask;?
? ? }?
? ? @Override?
? ? public void destroy() {?
? ? ? ? for (ScheduledTask task : this.scheduledTasks.values()) {?
? ? ? ? ? ? task.cancel();?
? ? ? ? }?
? ? ? ? this.scheduledTasks.clear();?
? ? }?
}
添加定时任务示例类
@Component("demoTask")?
public class DemoTask {?
? ? public void taskWithParams(String params) {?
? ? ? ? System.out.println("执行有参示例任务:" + params);?
? ? }?
? ? public void taskNoParams() {?
? ? ? ? System.out.println("执行无参示例任务");?
? ? }?
}
public class SysJobPO {?
? ? /**?
? ? * 任务ID?
? ? */?
? ? private Integer jobId;?
? ? /**?
? ? * bean名称?
? ? */?
? ? private String beanName;?
? ? /**?
? ? * 方法名称?
? ? */?
? ? private String methodName;?
? ? /**?
? ? * 方法参数?
? ? */?
? ? private String methodParams;?
? ? /**?
? ? * cron表达式?
? ? */?
? ? private String cronExpression;?
? ? /**?
? ? * 状态(1正常 0暂停)?
? ? */?
? ? private Integer jobStatus;?
? ? /**?
? ? * 备注?
? ? */?
? ? private String remark;?
? ? /**?
? ? * 创建时间?
? ? */?
? ? private Date createTime;?
? ? /**?
? ? * 更新时间?
? ? */?
? ? private Date updateTime;?
? ? public Integer getJobId() {?
? ? ? ? return jobId;?
? ? }?
? ? public void setJobId(Integer jobId) {?
? ? ? ? this.jobId = jobId;?
? ? }?
? ? public String getBeanName() {?
? ? ? ? return beanName;?
? ? }?
? ? public void setBeanName(String beanName) {?
? ? ? ? this.beanName = beanName;?
? ? }?
? ? public String getMethodName() {?
? ? ? ? return methodName;?
? ? }?
? ? public void setMethodName(String methodName) {?
? ? ? ? this.methodName = methodName;?
? ? }?
? ? public String getMethodParams() {?
? ? ? ? return methodParams;?
? ? }?
? ? public void setMethodParams(String methodParams) {?
? ? ? ? this.methodParams = methodParams;?
? ? }?
? ? public String getCronExpression() {?
? ? ? ? return cronExpression;?
? ? }?
? ? public void setCronExpression(String cronExpression) {?
? ? ? ? this.cronExpression = cronExpression;?
? ? }?
? ? public Integer getJobStatus() {?
? ? ? ? return jobStatus;?
? ? }?
? ? public void setJobStatus(Integer jobStatus) {?
? ? ? ? this.jobStatus = jobStatus;?
? ? }?
? ? public String getRemark() {?
? ? ? ? return remark;?
? ? }?
? ? public void setRemark(String remark) {?
? ? ? ? this.remark = remark;?
? ? }?
? ? public Date getCreateTime() {?
? ? ? ? return createTime;?
? ? }?
? ? public void setCreateTime(Date createTime) {?
? ? ? ? this.createTime = createTime;?
? ? }?
? ? public Date getUpdateTime() {?
? ? ? ? return updateTime;?
? ? }?
? ? public void setUpdateTime(Date updateTime) {?
? ? ? ? this.updateTime = updateTime;?
? ? }?
}
新增定时任务
boolean success = sysJobRepository.addSysJob(sysJob);?
if (!success)?
? ? return OperationResUtils.fail("新增失败");?
else {?
? ? if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {?
? ? ? ? SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());?
? ? ? ? cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());?
? ? }?
}?
return OperationResUtils.success();
修改定时任务,先移除原来的任务,再启动新任务
boolean success = sysJobRepository.editSysJob(sysJob);?
if (!success)?
? ? return OperationResUtils.fail("编辑失败");?
else {?
? ? //先移除再添加?
? ? if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {?
? ? ? ? SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());?
? ? ? ? cronTaskRegistrar.removeCronTask(task);?
? ? }?
? ? if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {?
? ? ? ? SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());?
? ? ? ? cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());?
? ? }?
}?
return OperationResUtils.success();
删除定时任务
boolean success = sysJobRepository.deleteSysJobById(req.getJobId());?
if (!success)?
? ? return OperationResUtils.fail("删除失败");?
else{?
? ? if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {?
? ? ? ? SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());?
? ? ? ? cronTaskRegistrar.removeCronTask(task);?
? ? }?
}?
return OperationResUtils.success();
定时任务启动/停止状态切换
if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {?
? ? SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());?
? ? cronTaskRegistrar.addCronTask(task, existedSysJob.getCronExpression());?
} else {?
? ? SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());?
? ? cronTaskRegistrar.removeCronTask(task);?
}
添加实现了CommandLineRunner接口的SysJobRunner类,当spring boot项目启动完成后,加载数据库里状态为正常的定时任务。
@Service?
public class SysJobRunner implements CommandLineRunner {?
? ? private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);?
? ? @Autowired?
? ? private ISysJobRepository sysJobRepository;?
? ? @Autowired?
? ? private CronTaskRegistrar cronTaskRegistrar;?
? ? @Override?
? ? public void run(String... args) {?
? ? ? ? // 初始加载数据库里状态为正常的定时任务?
? ? ? ? List<SysJobPO> jobList = sysJobRepository.getSysJobListByStatus(SysJobStatus.NORMAL.ordinal());?
? ? ? ? if (CollectionUtils.isNotEmpty(jobList)) {?
? ? ? ? ? ? for (SysJobPO job : jobList) {?
? ? ? ? ? ? ? ? SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());?
? ? ? ? ? ? ? ? cronTaskRegistrar.addCronTask(task, job.getCronExpression());?
? ? ? ? ? ? }?
? ? ? ? ? ? logger.info("定时任务已加载完毕...");?
? ? ? ? }?
? ? }?
}
工具类SpringContextUtils,用来从spring容器里获取bean
@Component?
public class SpringContextUtils implements ApplicationContextAware {?
? ? private static ApplicationContext applicationContext;?
? ? @Override?
? ? public void setApplicationContext(ApplicationContext applicationContext)?
? ? ? ? ? ? throws BeansException {?
? ? ? ? SpringContextUtils.applicationContext = applicationContext;?
? ? }?
? ? public static Object getBean(String name) {?
? ? ? ? return applicationContext.getBean(name);?
? ? }?
? ? public static <T> T getBean(Class<T> requiredType) {?
? ? ? ? return applicationContext.getBean(requiredType);?
? ? }?
? ? public static <T> T getBean(String name, Class<T> requiredType) {?
? ? ? ? return applicationContext.getBean(name, requiredType);?
? ? }?
? ? public static boolean containsBean(String name) {?
? ? ? ? return applicationContext.containsBean(name);?
? ? }?
? ? public static boolean isSingleton(String name) {?
? ? ? ? return applicationContext.isSingleton(name);?
? ? }?
? ? public static Class<extends Object> getType(String name) {?
? ? ? ? return applicationContext.getType(name);?
? ? }?
}