首页 > 数据库 >SpringBoot自带ThreadPoolTaskScheduler实现数据库管理定时任务

SpringBoot自带ThreadPoolTaskScheduler实现数据库管理定时任务

时间:2023-06-12 09:23:18浏览次数:56  
标签:task SpringBoot public String 任务 线程 定时 自带 ThreadPoolTaskScheduler

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

于是想到了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 定时任务线程配置

java复制代码/**
 * @author: jiangjs
 * @description: 需要添加ThreadPoolTaskScheduler线程池才能正常通过@Autowired/@Resource使用
 * @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 获取类工具

java复制代码/**
 * @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,其主要作用是获取数据参数,绑定定时任务类及定时任务方法,然后在通过反射拿到方法进行执行。参数则定义成泛型,便于直接传递,定时任务方法获取后不需要再次转换。

java复制代码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\<?>来进行相关操作,例如:调用关闭方法。

java复制代码    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、测试

创建的线程类与工具已经封装完成,接下来我们来进行下测试。

先创建定时任务表:

SQL复制代码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 创建定时任务执行类

java复制代码    /**
    * @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 创建实现方法

java复制代码    /**
     * @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来实现定时任务的删除,添加,具体实现参考上面代码。看看执行结果:

创建定时任务: 提交参数:

 

taskClazz:对应测试类 @Component(value = "testSchedulingTask")中的value值 taskMethod:测试内中的执行方法。如:TestSchedulingTask中的taskMethod方法 cron:定时任务的cron格式时间

 

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

删除定时任务:

 

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

 

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


总结

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

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

 

标签:task,SpringBoot,public,String,任务,线程,定时,自带,ThreadPoolTaskScheduler
From: https://www.cnblogs.com/caicz/p/17474071.html

相关文章

  • springboot+vue留守儿童爱心网站,附源码+数据库+论文+PPT,远程包安装运行
    1、项目介绍留守儿童爱心网站采用了B/S结构,JAVA作为开发语言,数据库采用了B/S结构,Mysql数据库进行开发。该系统包括前台操作,后台由管理员和用户两个部分,一方面,为用户提供首页、宣传新闻、志愿活动、爱心捐赠、个人中心、后台管理等功能;另一方面,为管理员提供首页、个人中心、用户管......
  • 针对SpringBoot服务端返回的空对象和空数组问题
    返回的Json会自动过滤掉空指针的对象,但是若遇到非空指针的没有任何内容的对象,举例如下:publicclassPerson{privateStringname;privateIntegerage;privateBooleansex;publicStringgetName(){returnname;}publicvoidsetNam......
  • SpringBoot进阶教程(七十六)多维度排序查询
    在项目中经常能遇到,需要对某些数据集合进行多维度排序的需求。对于集合多条件排序解决方案也有很多,今天我们就介绍一种,思路大致是设置一个分值的集合,这个分值是按照需求来设定大小的,再根据分值的大小对集合排序。v需求背景我们来模拟一个需求,现在需要查询一个用户列表,该列表......
  • Jdk 中自带的工具
    JDK(JavaDevelopmentKit)自带了很多工具,以下是一些常用的工具及其作用:jstat:用于监视JVM中各种运行时状态信息,如类加载、内存、垃圾回收等。jmap:用于导出JVM内存映像,可以用于内存泄漏分析、GC分析等。jstack:可以用于查看Java线程的堆栈信息,帮助定位线程死锁、线程......
  • 第二十九节:批量插入框架[Zack.EFCore.Batch]和EFCore7.x自带的批量删除、更新
    一.        二.        三.         !作       者:Yaopengfei(姚鹏飞)博客地址:http://www.cnblogs.com/yaopengfei/声     明1:如有错误,欢迎讨论,请勿谩骂^_^。声     明2:原创博客请在转载......
  • Springboot项目展示层级结构后端工具类
    publicclassMenuHelper{/***使用递归方法建菜单*@paramsysMenuList*@return*/publicstaticList<SysMenu>buildTree(List<SysMenu>sysMenuList){//存放最终数据List<SysMenu>trees=newArrayList<......
  • JAVA的springboot+vue家政服务管理平台,家政预约管理系统,附源码+数据库+论文+PPT
    1、项目介绍随着家政服务行业的不断发展,家政服务在现实生活中的使用和普及,家政服务行业成为近年内出现的一个新行业,并且能够成为大众广为认可和接受的行为和选择。设计家政服务管理平台的目的就是借助计算机让复杂的销售操作变简单,变高效。家政服务管理平台采用了B/S结构,JAVA作......
  • SpringBoot+Mysql+IDEA开发的社区医疗综合服务平台
    基于springboot的社区养老医疗综合服务平台项目介绍......
  • 解决layui框架自带的excel导出长数据变科学计数法
    项目中需要导出excel时,如果是大项目、要求高,当然使用第三方插件,或者后台导出是必要的,但是如果是一些小型项目,并且对导出excel样式要求不是很严格的,而且前端框架用的是layui的,layui框架自带的excel导出就成了我们最方便快捷的选择,但是在导出数据时会遇到一个问题:问题:layui框架自......
  • Layui自带的导出功能在导出身份证时后3位为000
    在使用Layui自带的导出功能进行数据导出操作时,会发现身份证号码会出现000的情况。 最开始在网上找资料,有人说要下载Excel的插件包也有说要修改js文件。搞半天还是不行。其实一行代码就ok,废话不多说自己上代码:  只需要在获取数据时加上以下代码即可 {field:'identity'......