如何使用 SpringBoot 实现动态增删启停定时任务?

简介: 我是小假 期待与你的下一次相遇 ~

在SpringBoot项目中,可以通过@EnableScheduling注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止任务。

要实现动态增删启停定时任务功能,比较广泛的做法是集成Quartz框架。但是在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂。

查看spring-context这个jar包中org.springframework.scheduling.ScheduledTaskRegistrar这个类的源代码,发现可以通过改造这个类就能实现动态增删启停定时任务功能。

添加执行定时任务的线程池配置类

  1. @Configuration  
  2. public class SchedulingConfig {  
  3.    @Bean  
  4.    public TaskScheduler taskScheduler() {  
  5.        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();  
  6.        // 定时任务执行线程池核心线程数  
  7.        taskScheduler.setPoolSize(4);  
  8.        taskScheduler.setRemoveOnCancelPolicy(true);  
  9.        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");  
  10.        return taskScheduler;  
  11.    }  
  12. }

添加ScheduledFuture的包装类。ScheduledFutureScheduledExecutorService定时任务线程池的执行结果。

  1. public final class ScheduledTask {  
  2.    volatile ScheduledFuture<?> future;  
  3.    /**  
  4.     * 取消定时任务  
  5.     */  
  6.    public void cancel() {  
  7.        ScheduledFuture<?> future = this.future;  
  8.        if (future != null) {  
  9.            future.cancel(true);  
  10.        }  
  11.    }  
  12. }

添加Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。

  1. public class SchedulingRunnable implements Runnable {  
  2.    private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);  
  3.    private String beanName;  
  4.    private String methodName;  
  5.    private String params;  
  6.    public SchedulingRunnable(String beanName, String methodName) {  
  7.        this(beanName, methodName, null);  
  8.    }  
  9.    public SchedulingRunnable(String beanName, String methodName, String params) {  
  10.        this.beanName = beanName;  
  11.        this.methodName = methodName;  
  12.        this.params = params;  
  13.    }  
  14.    @Override  
  15.    public void run() {  
  16.        logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);  
  17.        long startTime = System.currentTimeMillis();  
  18.        try {  
  19.            Object target = SpringContextUtils.getBean(beanName);  
  20.            Method method = null;  
  21.            if (StringUtils.isNotEmpty(params)) {  
  22.                method = target.getClass().getDeclaredMethod(methodName, String.class);  
  23.            } else {  
  24.                method = target.getClass().getDeclaredMethod(methodName);  
  25.            }  
  26.            ReflectionUtils.makeAccessible(method);  
  27.            if (StringUtils.isNotEmpty(params)) {  
  28.                method.invoke(target, params);  
  29.            } else {  
  30.                method.invoke(target);  
  31.            }  
  32.        } catch (Exception ex) {  
  33.            logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);  
  34.        }  
  35.        long times = System.currentTimeMillis() - startTime;  
  36.        logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);  
  37.    }  
  38.    @Override  
  39.    public boolean equals(Object o) {  
  40.        if (this == o) return true;  
  41.        if (o == null || getClass() != o.getClass()) return false;  
  42.        SchedulingRunnable that = (SchedulingRunnable) o;  
  43.        if (params == null) {  
  44.            return beanName.equals(that.beanName) &&  
  45.                    methodName.equals(that.methodName) &&  
  46.                    that.params == null;  
  47.        }  
  48.        return beanName.equals(that.beanName) &&  
  49.                methodName.equals(that.methodName) &&  
  50.                params.equals(that.params);  
  51.    }  
  52.    @Override  
  53.    public int hashCode() {  
  54.        if (params == null) {  
  55.            return Objects.hash(beanName, methodName);  
  56.        }  
  57.        return Objects.hash(beanName, methodName, params);  
  58.    }  
  59. }

添加定时任务注册类,用来增加、删除定时任务。

  1. @Component  
  2. public class CronTaskRegistrar implements DisposableBean {  
  3.    private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);  
  4.    @Autowired  
  5.    private TaskScheduler taskScheduler;  
  6.    public TaskScheduler getScheduler() {  
  7.        return this.taskScheduler;  
  8.    }  
  9.    public void addCronTask(Runnable task, String cronExpression) {  
  10.        addCronTask(new CronTask(task, cronExpression));  
  11.    }  
  12.    public void addCronTask(CronTask cronTask) {  
  13.        if (cronTask != null) {  
  14.            Runnable task = cronTask.getRunnable();  
  15.            if (this.scheduledTasks.containsKey(task)) {  
  16.                removeCronTask(task);  
  17.            }  
  18.            this.scheduledTasks.put(task, scheduleCronTask(cronTask));  
  19.        }  
  20.    }  
  21.    public void removeCronTask(Runnable task) {  
  22.        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);  
  23.        if (scheduledTask != null)  
  24.            scheduledTask.cancel();  
  25.    }  
  26.    public ScheduledTask scheduleCronTask(CronTask cronTask) {  
  27.        ScheduledTask scheduledTask = new ScheduledTask();  
  28.        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());  
  29.        return scheduledTask;  
  30.    }  
  31.    @Override  
  32.    public void destroy() {  
  33.        for (ScheduledTask task : this.scheduledTasks.values()) {  
  34.            task.cancel();  
  35.        }  
  36.        this.scheduledTasks.clear();  
  37.    }  
  38. }

添加定时任务示例类

  1. @Component("demoTask")  
  2. public class DemoTask {  
  3.    public void taskWithParams(String params) {  
  4.        System.out.println("执行有参示例任务:" + params);  
  5.    }  
  6.    public void taskNoParams() {  
  7.        System.out.println("执行无参示例任务");  
  8.    }  
  9. }

定时任务数据库表设计

添加定时任务实体类

  1. public class SysJobPO {  
  2.    /**  
  3.     * 任务ID  
  4.     */  
  5.    private Integer jobId;  
  6.    /**  
  7.     * bean名称  
  8.     */  
  9.    private String beanName;  
  10.    /**  
  11.     * 方法名称  
  12.     */  
  13.    private String methodName;  
  14.    /**  
  15.     * 方法参数  
  16.     */  
  17.    private String methodParams;  
  18.    /**  
  19.     * cron表达式  
  20.     */  
  21.    private String cronExpression;  
  22.    /**  
  23.     * 状态(1正常 0暂停)  
  24.     */  
  25.    private Integer jobStatus;  
  26.    /**  
  27.     * 备注  
  28.     */  
  29.    private String remark;  
  30.    /**  
  31.     * 创建时间  
  32.     */  
  33.    private Date createTime;  
  34.    /**  
  35.     * 更新时间  
  36.     */  
  37.    private Date updateTime;  
  38.    public Integer getJobId() {  
  39.        return jobId;  
  40.    }  
  41.    public void setJobId(Integer jobId) {  
  42.        this.jobId = jobId;  
  43.    }  
  44.    public String getBeanName() {  
  45.        return beanName;  
  46.    }  
  47.    public void setBeanName(String beanName) {  
  48.        this.beanName = beanName;  
  49.    }  
  50.    public String getMethodName() {  
  51.        return methodName;  
  52.    }  
  53.    public void setMethodName(String methodName) {  
  54.        this.methodName = methodName;  
  55.    }  
  56.    public String getMethodParams() {  
  57.        return methodParams;  
  58.    }  
  59.    public void setMethodParams(String methodParams) {  
  60.        this.methodParams = methodParams;  
  61.    }  
  62.    public String getCronExpression() {  
  63.        return cronExpression;  
  64.    }  
  65.    public void setCronExpression(String cronExpression) {  
  66.        this.cronExpression = cronExpression;  
  67.    }  
  68.    public Integer getJobStatus() {  
  69.        return jobStatus;  
  70.    }  
  71.    public void setJobStatus(Integer jobStatus) {  
  72.        this.jobStatus = jobStatus;  
  73.    }  
  74.    public String getRemark() {  
  75.        return remark;  
  76.    }  
  77.    public void setRemark(String remark) {  
  78.        this.remark = remark;  
  79.    }  
  80.    public Date getCreateTime() {  
  81.        return createTime;  
  82.    }  
  83.    public void setCreateTime(Date createTime) {  
  84.        this.createTime = createTime;  
  85.    }  
  86.    public Date getUpdateTime() {  
  87.        return updateTime;  
  88.    }  
  89.    public void setUpdateTime(Date updateTime) {  
  90.        this.updateTime = updateTime;  
  91.    }  
  92. }

新增定时任务

  1. boolean success = sysJobRepository.addSysJob(sysJob);  
  2. if (!success)  
  3.    return OperationResUtils.fail("新增失败");  
  4. else {  
  5.    if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {  
  6.        SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());  
  7.        cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());  
  8.    }  
  9. }  
  10. return OperationResUtils.success();

修改定时任务,先移除原来的任务,再启动新任务

  1. boolean success = sysJobRepository.editSysJob(sysJob);  
  2. if (!success)  
  3.    return OperationResUtils.fail("编辑失败");  
  4. else {  
  5.    //先移除再添加  
  6.    if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {  
  7.        SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());  
  8.        cronTaskRegistrar.removeCronTask(task);  
  9.    }  
  10.    if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {  
  11.        SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());  
  12.        cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());  
  13.    }  
  14. }  
  15. return OperationResUtils.success();

删除定时任务

  1. boolean success = sysJobRepository.deleteSysJobById(req.getJobId());  
  2. if (!success)  
  3.    return OperationResUtils.fail("删除失败");  
  4. else{  
  5.    if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {  
  6.        SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());  
  7.        cronTaskRegistrar.removeCronTask(task);  
  8.    }  
  9. }  
  10. return OperationResUtils.success();

定时任务启动/停止状态切换

  1. if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {  
  2.    SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());  
  3.    cronTaskRegistrar.addCronTask(task, existedSysJob.getCronExpression());  
  4. } else {  
  5.    SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());  
  6.    cronTaskRegistrar.removeCronTask(task);  
  7. }

添加实现了CommandLineRunner接口的SysJobRunner类,当SpringBoot项目启动完成后,加载数据库里状态为正常的定时任务。

  1. @Service  
  2. public class SysJobRunner implements CommandLineRunner {  
  3.    private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);  
  4.    @Autowired  
  5.    private ISysJobRepository sysJobRepository;  
  6.    @Autowired  
  7.    private CronTaskRegistrar cronTaskRegistrar;  
  8.    @Override  
  9.    public void run(String... args) {  
  10.        // 初始加载数据库里状态为正常的定时任务  
  11.        List<SysJobPO> jobList = sysJobRepository.getSysJobListByStatus(SysJobStatus.NORMAL.ordinal());  
  12.        if (CollectionUtils.isNotEmpty(jobList)) {  
  13.            for (SysJobPO job : jobList) {  
  14.                SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());  
  15.                cronTaskRegistrar.addCronTask(task, job.getCronExpression());  
  16.            }  
  17.            logger.info("定时任务已加载完毕...");  
  18.        }  
  19.    }  
  20. }

工具类SpringContextUtils,用来从Spring容器里获取bean

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


相关文章
|
Java 调度 Spring
SpringBoot实现多线程定时任务动态定时任务配置文件配置定时任务
SpringBoot实现多线程定时任务动态定时任务配置文件配置定时任务
1513 0
|
10月前
|
缓存 Java 索引
|
Java Spring
动态控制 Spring Boot 中的 @Scheduled 定时任务
Spring Boot 中的 @Scheduled 注解为定时任务提供了一种很简单的实现,只需要在注解中加上一些属性,例如 fixedRate、fixedDelay、cron(最常用)等等,并且在启动类上面加上 @EnableScheduling 注解,就可以启动一个定时任务了。 但是在某些情况下,并没有这么简单,例如项目部署上线之后,我们可能会修改定时任务的执行时间,并且停止、重启定时任务等,因为定时任务是直接写死在程序中的,修改起来不是非常的方便。所以,简单记录一下自己的一些解决方案,仅供参考。
2809 0
|
10月前
|
存储 监控 Cloud Native
云原生监控实战:Prometheus+Grafana打造RDS多维度预警体系
本方案构建了基于Prometheus与Thanos的云原生RDS监控体系,涵盖数据采集、存储、可视化与告警全流程。支持10万+QPS采集、90%存储压缩,具备&lt;30秒告警延迟能力。通过自定义指标与智能预警策略,显著提升故障发现效率,实现分钟级响应。
706 5
|
10月前
|
Java Spring
使用 Spring Boot 多个定时任务阻塞问题的解决方案
我是小假 期待与你的下一次相遇 ~
552 5
|
10月前
|
Prometheus 监控 Cloud Native
|
Java API 调度
SpringBoot整合XXL-JOB【01】- 初识XXL-JOB
XXL-JOB 是一个分布式任务调度平台,设计目标为开发迅速、学习简单、轻量级、易扩展。它解决了分布式环境下定时任务重复执行的问题,无需额外加锁,降低了维护成本。XXL-JOB 由调度中心和执行器两部分组成,前者管理任务,后者执行具体逻辑,使代码结构更清晰。适用于多机部署场景,支持统一管理任务的启停和频率调整。
2428 8
SpringBoot整合XXL-JOB【01】- 初识XXL-JOB
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
1295 4
|
Java BI 调度
Springboot项目中定时任务的四种实现方式
Springboot项目中定时任务的四种实现方式
3093 4
|
Java 调度 Maven
Springboot实战篇--Springboot框架通过@Scheduled实现定时任务
Spring Boot的Scheduled定时任务无需额外Maven依赖,通过`@EnableScheduling`开启。任务调度有两种方式:fixedRate和fixedDelay,前者任务结束后立即按设定间隔执行,后者在任务完成后等待设定时间再执行。更灵活的是cron表达式,例如`0 0 3 * * ?`表示每天3点执行。实现定时任务时,需注意默认单线程执行可能导致的任务交错,可通过自定义线程池解决。
1805 0

热门文章

最新文章

下一篇
开通oss服务