SpringBoot+ThreadPoolTaskScheduler,定时任务还可以这么玩~

戳上方蓝字“Java面试题精选”关注!

最近做了一个需求:将定时任务保存到数据库中,并在页面上实现定时任务的开关,以及更新定时任务时间后重新创建定时任务。

于是想到了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<Timplements 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(200not null comment '定时任务类',
    task_method varchar(200not null comment '定时任务执行方法',
    cron varchar(200not 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 执行结果

我们创建了三个方法,分别是:addTaskeditTaskdeleteTask来实现定时任务的删除,添加,具体实现参考上面代码。看看执行结果:

创建定时任务:

提交参数:

SpringBoot+ThreadPoolTaskScheduler,定时任务还可以这么玩~
  • taskClazz: 对应测试类 @Component(value = "testSchedulingTask")中的value值
  • taskMethod: 测试内中的执行方法。如:TestSchedulingTask中的taskMethod方法
  • cron: 定时任务的cron格式时间
SpringBoot+ThreadPoolTaskScheduler,定时任务还可以这么玩~

从执行结果动态图可以看到,任务每隔5s种就会获取一次用户信息。

删除定时任务:

SpringBoot+ThreadPoolTaskScheduler,定时任务还可以这么玩~

删除Id为1000的数据库任务,同时也是删除key为1000的定时任务

SpringBoot+ThreadPoolTaskScheduler,定时任务还可以这么玩~


任务被删除后,即使过了5s依然没有任务在执行。

3总结

ThreadPoolTaskScheduler实现定时任务主要是通过对线程的管理来进行操作,添加任务时即创建一个线程,删除时即将该线程删除。因此在创建定时任务只需要创建线程就可以,在创建线程时,通过反射来获取对应的方法及传递参数。

上述就是使用SprngBoot中的ThreadPoolTaskScheduler来实现定时任务,我们只要使用前端连接相应的接口就可以实现管理人员管理定时任务的功能。

  • 源码地址:https://github.com/lovejiashn/schedule_task.git

来源:juejin.cn/post/7242105983135514685


后端专属技术群

构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!

文明发言,以交流技术职位内推行业探讨为主

广告人士勿入,切勿轻信私聊,防止被骗

SpringBoot+ThreadPoolTaskScheduler,定时任务还可以这么玩~

加我好友,拉你进群

原文始发于微信公众号(Java面试题精选):SpringBoot+ThreadPoolTaskScheduler,定时任务还可以这么玩~

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/202840.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!