@IT·互联网技术研发汇集

SpringBoot实现动态增删启停定时任务

2024-02-28  本文已影响0人  知信学源

在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); 

    } 

}

上一篇 下一篇

猜你喜欢

热点阅读