Hi,我们好,我是抢老婆酸奶的小肥仔。

最近做了一个需求:将守时使命保存到数据库中,并在页面上完结守时使命的开关,以及更新守时使命时刻后从头创立守时使命。

于是想到了SpringBoot中自带的ThreadPoolTaskScheduler。

在SpringBoot中供给的ThreadPoolTaskScheduler这个类,该类供给了一个schedule(Runnable task, Trigger trigger)的办法能够完结守时使命的创立,该办法是经过办理线程来完结。

schedule(Runnable task, Trigger trigger)源码

public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
            ScheduledExecutorService executor = getScheduledExecutor();
            try {
                    ErrorHandler errorHandler = this.errorHandler;
                    if (errorHandler == null) {
                            errorHandler = TaskUtils.getDefaultErrorHandler(true);
                    }
                    return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
            }
            catch (RejectedExecutionException ex) {
                    throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
            }
    }

上述源码中,首先会获取守时使命履行服务,即:ScheduledExecutorService executor = getScheduledExecutor(); ,然后创立重排线程类,并调用schedule() 办法来创立ScheduledFuture<?>

ScheduledFuture<?> 中供给了cancel(boolean mayInterruptIfRunning) 来完结守时使命的删去。经过这两个办法,咱们能够完结上述的需求。

废话不多说,代码撸起。

1、代码完结

1.1 守时使命线程配置

/**
 * @author: jiangjs
 * @description: 
 * @date: 2023/2/17 9:51
 **/
@Configuration
public class SchedulingTaskConfig {
    @Bean(name = "taskSchedulerPool")
    public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        //设置线程池大小
        taskScheduler.setPoolSize(60);
        //线程称号前缀
        taskScheduler.setThreadNamePrefix("task-pool-");
        //设置停止时等待最大时刻,单位s,即在封闭时,需等待其他使命完结履行
        taskScheduler.setAwaitTerminationSeconds(3000);
        //设置关机时是否等待计划使命完结,不中断正在运转的使命并履行队列中的所有使命,默许false
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        return taskScheduler;
    }
}

1.2 获取类东西

/**
 * @author: jiangjs
 * @description: 上下文获取类
 * @date: 2023/1/30 10:28
 **/
@Component
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextUtils.context = applicationContext;
    }
    public static Object getBean(String name){
        return context.getBean(name);
    }
}

经过ApplicationContext中getBean经过类名来获取对应的类。

1.3 创立守时使命线程类

因为ThreadPoolTaskScheduler是根据线程来创立守时使命的,因而咱们封装一个类来完结Runnable,其首要效果是获取数据参数,绑定守时使命类及守时使命办法,然后在经过反射拿到办法进行履行。参数则界说成泛型,便于直接传递,守时使命办法获取后不需要再次转化。

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.Objects;
/**
 * @author: jiangjs
 * @description: 完结守时使命线程
 * @date: 2023/2/16 15:31
 **/
@Slf4j
public class SchedulingTaskRunnable<T> implements Runnable {
    /**
     * 其他参数
     */
    private final T t;
    /**
     * 守时使命类
     */
    private final String clazz;
    /**
     * 守时使命办法
     */
    private final String methodName;
    SchedulingTaskRunnable(T t,String clazz,String methodName){
        this.t = t;
        this.clazz = clazz;
        this.methodName = methodName;
    }
    @Override
    public void run() {
        Object bean = SpringContextUtils.getBean(clazz);
        Method method;
        try{
            method = Objects.nonNull(t) ? bean.getClass().getDeclaredMethod(methodName,t.getClass()) : bean.getClass().getDeclaredMethod(methodName);
            ReflectionUtils.makeAccessible(method);
            if (Objects.nonNull(t)){
                method.invoke(bean,t);
            } else {
                method.invoke(bean);
            }
        }catch (Exception e){
            log.error("获取办法信息报错:{}",e.getMessage());
        }
    }
}

1.4 封装办理守时使命东西

该东西首要完结守时使命的创立和删去办法,在创立守时使命时要先调用删去办法,保证当时使命是仅有的,因而在更新守时使命时刻时,只需要调用创立办法即可。

其间界说了一个 ConcurrentHashMap<String, ScheduledFuture<?>> ,首要效果是为了办理守时使命,经过自界说的使命称号或Id,获取到ScheduledFuture\<?>来进行相关操作,例如:调用封闭办法。

    import lombok.extern.slf4j.Slf4j;
    import org.apache.poi.ss.formula.functions.T;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
    import org.springframework.scheduling.support.CronTrigger;
    import org.springframework.stereotype.Component;
    import java.util.Objects;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ScheduledFuture;
    /**
     * @author: jiangjs
     * @description: 守时使命办理办法
     * @date: 2023/2/16 15:48
     **/
    @Component
    @Slf4j
    public class SchedulingTaskManage {
        /**
         * 将使命放入map便于办理
         */
        private final ConcurrentHashMap<String, ScheduledFuture<?>> cache = new ConcurrentHashMap<>();
        @Resource(name = "taskSchedulerPool")
        private ThreadPoolTaskScheduler threadPoolTaskScheduler;
        /**
         * 删去守时使命
         * @param key 自界说守时使命称号
         */
        public void stopSchedulingTask(String key){
            if (Objects.isNull(cache.get(key))){
                log.info(String.format(".......当时key【%s】没有守时使命......",key));
                return;
            }
            ScheduledFuture<?> future = cache.get(key);
            if (Objects.nonNull(future)){
                //封闭当时守时使命
                future.cancel(Boolean.TRUE);
                cache.remove(key);
                log.info(String.format("删去【%s】对应守时使命成功",key));
            }
        }
        /**
         * 创立守时使命
         * @param key 使命key
         * @param runnable 当时线程
         * @param cron 守时使命cron
         */
        public void createSchedulingTask(String key,SchedulingTaskRunnable<T> runnable, String cron){
            ScheduledFuture<?> schedule = taskScheduler.schedule(runnable, new CronTrigger(cron));
            assert schedule != null;
            cache.put(key,schedule);
            log.info(String.format("【%s】创立守时使命成功",key));
        }
    }

2、测验

创立的线程类与东西现已封装完结,接下来咱们来进行下测验。

先创立守时使命表:

create table t_schedule_task(
    id int auto_increment primary key not null comment '主键Id',
    task_clazz varchar(200) not null comment '守时使命类',
    task_method varchar(200) not null comment '守时使命履行办法',
    cron varchar(200) not null comment '守时使命时刻',
    status smallint not null default 0 comment '守时使命状态,0:开启,1:封闭'
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 comment '守时使命办理表';

2.1 创立守时使命履行类

    /**
    * @author: jiangjs
    * @description:
    * @date: 2023/2/16 16:24
    **/
    @Slf4j
    @Component(value = "testSchedulingTask")
    public class TestSchedulingTask {
        public void taskMethod(UserInfo obj){
            log.info(String.format("调用了当时守时使命:输出参数:参数1:%s,参数2:%s",obj.getUserName(),obj.getPassword()));
        }
    }

简略获取用户信息,进行显示。

2.2 创立完结办法

    /**
     * @author: jiangjs
     * @description:
     * @date: 2023/1/12 10:53
     **/
    @Service
    public class ScheduledTaskManageServiceImpl implements ScheduledTaskManageService {
        @Autowired
        private SchedulingTaskManage taskManage;
        @Resource
        private ScheduleTaskMapper scheduleTaskMapper;
        @Override
        public ResultUtil<?> addTask(ScheduleTask task) {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserName("张三");
            userInfo.setPassword("121212121212");
            String cron = task.getCron();
            boolean validExpression = CronExpression.isValidExpression(cron);
            if (!validExpression){
                return ResultUtil.error("无效的cron格局,请从头填写");
            }
            scheduleTaskMapper.insert(task);
            SchedulingTaskRunnable<UserInfo> taskRunnable = new SchedulingTaskRunnable<>(userInfo, task.getTaskClazz(), task.getTaskMethod());
            taskManage.createSchedulingTask(String.valueOf(task.getId()), taskRunnable,task.getCron());
            return ResultUtil.success();
        }
        @Override
        public ResultUtil<?> deleteTask(Integer id) {
            scheduleTaskMapper.deleteById(id);
            taskManage.stopSchedulingTask(String.valueOf(id));
            return ResultUtil.success();
        }
        @Override
        public ResultUtil<?> editTask(ScheduleTask task) {
            scheduleTaskMapper.updateById(task);
            UserInfo userInfo = new UserInfo();
            userInfo.setUserName("张三");
            userInfo.setPassword("33333333");
            SchedulingTaskRunnable<UserInfo> taskRunnable = new SchedulingTaskRunnable<>(userInfo, task.getTaskClazz(), task.getTaskMethod());
            taskManage.createSchedulingTask(String.valueOf(task.getId()), taskRunnable,task.getCron());
            return ResultUtil.success();
        }
    }

上述代码中ScheduleTaskMapper是继承Mybatis-plus中的BaseMapper完结单表操作,小伙伴们可自行完结。

2.3 履行成果

咱们创立了三个办法,分别是: addTask,editTask,deleteTask来完结守时使命的删去,增加,详细完结参考上面代码。看看履行成果:

创立守时使命: 提交参数:

定时任务还可以这么玩--基于SpringBoot中的ThreadPoolTaskScheduler实现

taskClazz:对应测验类 @Component(value = “testSchedulingTask”)中的value值 taskMethod:测验内里的履行办法。如:TestSchedulingTask中的taskMethod办法 cron:守时使命的cron格局时刻

定时任务还可以这么玩--基于SpringBoot中的ThreadPoolTaskScheduler实现

从履行成果动态图能够看到,使命每隔5s种就会获取一次用户信息。

删去守时使命:

定时任务还可以这么玩--基于SpringBoot中的ThreadPoolTaskScheduler实现

删去Id为1000的数据库使命,一起也是删去key为1000的守时使命

定时任务还可以这么玩--基于SpringBoot中的ThreadPoolTaskScheduler实现

使命被删去后,即使过了5s仍然没有使命在履行。


总结

ThreadPoolTaskScheduler完结守时使命首要是经过对线程的办理来进行操作,增加使命时即创立一个线程,删去时即将该线程删去。因而在创立守时使命只需要创立线程就能够,在创立线程时,经过反射来获取对应的办法及传递参数。

上述就是运用SprngBoot中的ThreadPoolTaskScheduler来完结守时使命,咱们只需运用前端衔接相应的接口就能够完结办理人员办理守时使命的功能。

今日就共享这么多,谢谢我们听我叨叨。

源码地址:github.com/lovejiashn/…