SpringBoot dynamically adds scheduled tasks

This article has participated in the "Newcomer Creation Ceremony" event to start the road of gold creation together.

 The recent requirement has an automatic release function, and it is necessary to dynamically add a scheduled task for each submission.

code structure

 1. Configuration class

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;
    }

}
复制代码

  1. Timed task type enumeration
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);

}
复制代码

The function of this enumeration is to generate the runnable of the timed task and the unique value of the timed task, which is convenient for subsequent maintenance.

  1. The actual execution task implementation class
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);
    }

}
复制代码

  1. Scheduled task wrapper
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);
        }
    }

}
复制代码

The role of this class is to wrap the actual execution task and provide the execution method of the scheduler

  1. Task Registrar (Core)
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();
    }

}
复制代码

This class provides the api for executing and submitting tasks, and implements the DisposableBean interface, so that tasks can be destroyed together when the bean is destroyed.

  1. use

    @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;
    }
复制代码

At last

       This is a simple tool for dynamically adding timed tasks. There is a lot of room for improvement. For example, if you want to persist, you can insert it into the library, define a CommandLineRunner to load all timed tasks at startup, and add hooks to tasks to automatically submit and automatically Delete, etc., the code directly cv will report an error, just some tools, constant class will report an error, just change it, I have personally tested it, and if you have any questions, you can communicate in the comment area

Guess you like

Origin juejin.im/post/7088852395328798734