SpringBoot 动态添加定时任务

最近的需求有一个自动发布的功能, 需要做到每次提交都要动态的添加一个定时任务

 

代码结构

 

 1. 配置类

package com.orion.ops.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * 调度器配置
 *
 * @author Jiahang Li
 * @version 1.0.0
 * @since 2022/2/14 9:51
 */
@EnableScheduling
@Configuration
public class SchedulerConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(4);
        scheduler.setRemoveOnCancelPolicy(true);
        scheduler.setThreadNamePrefix("scheduling-task-");
        return scheduler;
    }

}

2. 定时任务类型枚举

package com.orion.ops.handler.scheduler;

import com.orion.ops.consts.Const;
import com.orion.ops.handler.scheduler.impl.ReleaseTaskImpl;
import com.orion.ops.handler.scheduler.impl.SchedulerTaskImpl;
import lombok.AllArgsConstructor;

import java.util.function.Function;

/**
 * 任务类型
 *
 * @author Jiahang Li
 * @version 1.0.0
 * @since 2022/2/14 10:16
 */
@AllArgsConstructor
public enum TaskType {

    /**
     * 发布任务
     */
    RELEASE(id -> new ReleaseTaskImpl((Long) id)) {
        @Override
        public String getKey(Object params) {
            return Const.RELEASE + "-" + params;
        }
    },

    /**
     * 调度任务
     */
    SCHEDULER_TASK(id -> new SchedulerTaskImpl((Long) id)) {
        @Override
        public String getKey(Object params) {
            return Const.TASK + "-" + params;
        }
    },

    ;

    private final Function<Object, Runnable> factory;

    /**
     * 创建任务
     *
     * @param params params
     * @return task
     */
    public Runnable create(Object params) {
        return factory.apply(params);
    }

    /**
     * 获取 key
     *
     * @param params params
     * @return key
     */
    public abstract String getKey(Object params);

}

这个枚举的作用是生成定时任务的 runnable 和 定时任务的唯一值, 方便后续维护

3. 实际执行任务实现类

package com.orion.ops.handler.scheduler.impl;

import com.orion.ops.service.api.ApplicationReleaseService;
import com.orion.spring.SpringHolder;
import lombok.extern.slf4j.Slf4j;

/**
 * 发布任务实现
 *
 * @author Jiahang Li
 * @version 1.0.0
 * @since 2022/2/14 10:25
 */
@Slf4j
public class ReleaseTaskImpl implements Runnable {

    protected static ApplicationReleaseService applicationReleaseService = SpringHolder.getBean(ApplicationReleaseService.class);

    private Long releaseId;

    public ReleaseTaskImpl(Long releaseId) {
        this.releaseId = releaseId;
    }

    @Override
    public void run() {
        log.info("定时执行发布任务-触发 releaseId: {}", releaseId);
        applicationReleaseService.runnableAppRelease(releaseId, true);
    }

}

4. 定时任务包装器

package com.orion.ops.handler.scheduler;

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;

import java.util.Date;
import java.util.concurrent.ScheduledFuture;

/**
 * 定时 任务包装器
 *
 * @author Jiahang Li
 * @version 1.0.0
 * @since 2022/2/14 10:34
 */
public class TimedTask {

    /**
     * 任务
     */
    private Runnable runnable;

    /**
     * 异步执行
     */
    private volatile ScheduledFuture<?> future;

    public TimedTask(Runnable runnable) {
        this.runnable = runnable;
    }

    /**
     * 提交任务 一次性
     *
     * @param scheduler scheduler
     * @param time      time
     */
    public void submit(TaskScheduler scheduler, Date time) {
        this.future = scheduler.schedule(runnable, time);
    }

    /**
     * 提交任务 cron表达式
     *
     * @param trigger   trigger
     * @param scheduler scheduler
     */
    public void submit(TaskScheduler scheduler, Trigger trigger) {
        this.future = scheduler.schedule(runnable, trigger);
    }

    /**
     * 取消定时任务
     */
    public void cancel() {
        if (future != null) {
            future.cancel(true);
        }
    }

}

这个类的作用是包装实际执行任务, 以及提供调度器的执行方法

5. 任务注册器 (核心)

package com.orion.ops.handler.scheduler;

import com.orion.ops.consts.MessageConst;
import com.orion.utils.Exceptions;
import com.orion.utils.collect.Maps;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;

/**
 * 任务注册器
 *
 * @author Jiahang Li
 * @version 1.0.0
 * @since 2022/2/14 10:46
 */
@Component
public class TaskRegister implements DisposableBean {

    private final Map<String, TimedTask> taskMap = Maps.newCurrentHashMap();

    @Resource
    @Qualifier("taskScheduler")
    private TaskScheduler scheduler;

    /**
     * 提交任务
     *
     * @param type   type
     * @param time   time
     * @param params params
     */
    public void submit(TaskType type, Date time, Object params) {
        // 获取任务
        TimedTask timedTask = this.getTask(type, params);
        // 执行任务
        timedTask.submit(scheduler, time);
    }

    /**
     * 提交任务
     *
     * @param type   type
     * @param cron   cron
     * @param params params
     */
    public void submit(TaskType type, String cron, Object params) {
        // 获取任务
        TimedTask timedTask = this.getTask(type, params);
        // 执行任务
        timedTask.submit(scheduler, new CronTrigger(cron));
    }

    /**
     * 获取任务
     *
     * @param type   type
     * @param params params
     */
    private TimedTask getTask(TaskType type, Object params) {
        // 生成任务
        Runnable runnable = type.create(params);
        String key = type.getKey(params);
        // 判断是否存在任务
        if (taskMap.containsKey(key)) {
            throw Exceptions.init(MessageConst.TASK_PRESENT);
        }
        TimedTask timedTask = new TimedTask(runnable);
        taskMap.put(key, timedTask);
        return timedTask;
    }

    /**
     * 取消任务
     *
     * @param type   type
     * @param params params
     */
    public void cancel(TaskType type, Object params) {
        String key = type.getKey(params);
        TimedTask task = taskMap.get(key);
        if (task != null) {
            taskMap.remove(key);
            task.cancel();
        }
    }

    /**
     * 是否存在
     *
     * @param type   type
     * @param params params
     */
    public boolean has(TaskType type, Object params) {
        return taskMap.containsKey(type.getKey(params));
    }

    @Override
    public void destroy() {
        taskMap.values().forEach(TimedTask::cancel);
        taskMap.clear();
    }

}

这个类提供了执行, 提交任务的api, 实现 DisposableBean 接口, 便于在bean销毁时将任务一起销毁

6. 使用


    @Resource
    private TaskRegister taskRegister;
    
    /**
     * 提交发布
     */
    @RequestMapping("/submit")
    @EventLog(EventType.SUBMIT_RELEASE)
    public Long submitAppRelease(@RequestBody ApplicationReleaseRequest request) {
        Valid.notBlank(request.getTitle());
        Valid.notNull(request.getAppId());
        Valid.notNull(request.getProfileId());
        Valid.notNull(request.getBuildId());
        Valid.notEmpty(request.getMachineIdList());
        TimedReleaseType timedReleaseType = Valid.notNull(TimedReleaseType.of(request.getTimedRelease()));
        if (TimedReleaseType.TIMED.equals(timedReleaseType)) {
            Date timedReleaseTime = Valid.notNull(request.getTimedReleaseTime());
            Valid.isTrue(timedReleaseTime.compareTo(new Date()) > 0, MessageConst.TIMED_GREATER_THAN_NOW);
        }
        // 提交
        Long id = applicationReleaseService.submitAppRelease(request);
        // 提交任务
        if (TimedReleaseType.TIMED.equals(timedReleaseType)) {
            taskRegister.submit(TaskType.RELEASE, request.getTimedReleaseTime(), id);
        }
        return id;
    }

最后

       这是一个简单的动态添加定时任务的工具, 有很多的改造空间, 比如想持久化可以插入到库中, 定义一个 CommandLineRunner 在启动时将定时任务全部加载, 还可以给任务加钩子自动提交,自动删除等, 代码直接cv一定会报错, 就是一些工具, 常量类会报错, 改改就好了, 本人已亲测可用, 有什么问题可以在评论区沟通

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring Boot提供了一个方便的方式来动态添加定时任务。您可以使用Spring的@Scheduled注释来定义定时任务,并使用Spring的TaskScheduler接口来动态添加和删除任务。 首先,您需要定义一个定时任务类,该类应该包含一个方法,该方法将在指定的时间间隔内运行。您可以使用@Scheduled注释来定义方法的运行时间。例如,以下代码定义了一个每分钟运行一次的定时任务: ``` @Component public class MyTask { @Scheduled(cron = "0 * * * * ?") public void run() { // do something } } ``` 接下来,您需要使用TaskScheduler接口来动态添加和删除任务。您可以使用ScheduledFuture接口来跟踪任务的状态,并使用TaskScheduler.schedule方法来添加任务。例如,以下代码动态添加了一个每5分钟运行一次的定时任务: ``` @Component public class TaskScheduler { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; private ScheduledFuture<?> scheduledFuture; public void addTask() { scheduledFuture = threadPoolTaskScheduler.schedule(new MyTask(), new CronTrigger("0 */5 * * * *")); } public void removeTask() { if (scheduledFuture != null) { scheduledFuture.cancel(true); } } } ``` 在上面的代码中,TaskScheduler类使用@Autowired注释注入了一个ThreadPoolTaskScheduler实例,该实例是Spring Boot提供的一个线程池。然后,TaskScheduler类定义了一个addTask方法,该方法使用TaskScheduler.schedule方法添加了一个每5分钟运行一次的定时任务。TaskScheduler类还定义了一个removeTask方法,该方法使用ScheduledFuture.cancel方法取消了任务的执行。 最后,您需要在Spring Boot应用程序的配置文件中配置ThreadPoolTaskScheduler。例如,以下代码配置了一个最大线程数为10的线程池: ``` @Configuration @EnableScheduling public class AppConfig { @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(10); threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler"); return threadPoolTaskScheduler; } } ``` 在上面的代码中,AppConfig类使用@Bean注释定义了一个ThreadPoolTaskScheduler实例,并使用setPoolSize方法设置了最大线程数为10。然后,AppConfig类使用@EnableScheduling注释启用了Spring定时任务功能。 现在,您可以在Spring Boot应用程序中动态添加和删除定时任务了。只需调用TaskScheduler类的addTask和removeTask方法即可。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值