SpringTask实现数据库中获取任务、调用方法、方法参数

1. 引言

quartz对数据库支持非常强大,但是用起来并没有SpringTask那么简单。
因此,个人造了一点小轮子,让SpringTask拥有类似quartz的功能(当然没有那么完善)。

转载请注明出处,欢迎留言交流。

2. 数据表设计

在这里插入图片描述

设计思路:
exec_time字段提供对固定时间执行一次的支持,也可以通过cron字段,实现任意触发时间。

method_name字段表示需要触发的方法名;
args则是method_name对应方法的参数值;
args_type则是args的具体类型(暂时仅支持基本数据类型以及包装类)。

3. 代码

注入TaskScheduler


@Configuration
@EnableScheduling
public class SchedulerConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        //线程池大小
        scheduler.setPoolSize(10);
        //线程名字前缀
        scheduler.setThreadNamePrefix("spring-task-thread");
        return scheduler;
    }
}

执行引擎

@Component
public class JobEngine {

    @Autowired
    TaskScheduler scheduler;

    public void execute(Runnable runnable, Date date){
        // SpringTask不支持年  Seconds Minutes Hours DayofMonth Month DayofWeek
        String cron = DateUtil.format(date, "ss mm HH dd MM ?");
        scheduler.schedule(runnable,new CronTrigger(cron));
    }

    public void execute(Runnable runnable, String cron){
        scheduler.schedule(runnable,new CronTrigger(cron));
    }
}

执行器

/**
 * 定时扫描数据库执行任务
 */
@Slf4j
@Component
public class JobService {

    @Autowired
    AppJobMapper jobMapper;
    
    @Autowired
    JobEngine jobEngine;

    @Scheduled(cron = "*/5 * * * * ?")
    public void execute() {
        // 查询出所有未执行的任务 isExec=0 && now<execTime
        List<AppJob> jobs = jobMapper.findTask();
        if (jobs.size() != 0) {
            jobs.forEach(job -> {
                // 获取数据库数据
                String[] strArgs = job.getArgs().split(",");
                Date date = job.getExecTime();
                String methodName = job.getMethodName();
                String[] classTypes = job.getArgsType().split(",");

				// 反射装配参数以及对应类型
                List<Class<?>> argsTypeList = new ArrayList<>();
                List<Object> argsList = new ArrayList<>();
                for(int i=0;i<strArgs.length;i++){
                    String classType = classTypes[i];

                    if ("string".equalsIgnoreCase(classType)) {
                        classType = "java.lang.String";
                        argsList.add(String.valueOf(strArgs[i]));
                    }else if ("integer".equalsIgnoreCase(classType) || "int".equalsIgnoreCase(classType)) {
                        classType = "java.lang.Integer";
                        argsList.add(Integer.valueOf(strArgs[i]));
                    }else if ("long".equalsIgnoreCase(classType)) {
                        classType = "java.lang.Long";
                        argsList.add(Long.valueOf(strArgs[i]));
                    }else if ("double".equalsIgnoreCase(classType)) {
                        classType = "java.lang.Double";
                        argsList.add(Double.valueOf(strArgs[i]));
                    }else if ("boolean".equalsIgnoreCase(classType)) {
                        classType = "java.lang.Boolean";
                        argsList.add(Boolean.valueOf(strArgs[i]));
                    }
                    try {
                        Class<?> aClass = Class.forName(classType);
                        argsTypeList.add(aClass);
                    } catch (ClassNotFoundException e) {
                        log.error("只支持基本的数据类型以及包装类,非法类型:{}",classType);
                    }
                }
				// List转为Array,invoke要求
                Object[] args = new Object[argsList.size()];
                Class<?>[] classes = new Class[argsTypeList.size()];
                argsTypeList.toArray(classes);
                argsList.toArray(args);

                // 创建定时任务
                log.info("创建定时任务{}", job);
                Runnable runnable = () -> {
                    // 可能已经被执行,检查isExec是否为1
                    if (jobMapper.beforeExec(job.getJobId()) !=null) {
                        log.info("定时任务{}已被执行", job);
                        return;
                    }
                    try {
                        Method method = this.getClass().getMethod(methodName, classes);
                        if (strArgs.length > 0) {
                            method.invoke(this, args);
                        } else {
                            method.invoke(this);
                        }
                        // 更新状态 设isExec=1
                        if (jobMapper.afterExec(job.getJobId()) != 1) {
                            log.error("定时任务{}执行完毕后状态更新失败", job);
                        }
                    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                        log.error("定时任务{}执行完毕失败:{}", job,e.getMessage());
                    }
                };
                if (date == null) {
                    jobEngine.execute(runnable, job.getCron());
                } else {
                    jobEngine.execute(runnable, date);
                }
            });
        }
    }
    
    public void test(Integer a, Double b) {
        System.out.println("扫描数据库执行测试方法,参数:" + a + b);
    }
}

4. 用途

  1. 将时间跨度较高的任务加到数据表中(比如一个月执行一次),由JobService.execute方法,定时扫描数据库执行,能够避免服务停止导致的定时任务丢失。
  2. 可以将JobService.execute方法中的代码抽出,作为其他工具类使用(比如按扫描频度、执行功能等增加多种不同的execute)
  3. 本人目前的使用步骤:
    1. 在JobService类中添加可能的方法
    2. 在满足创建定时任务的地方,通过JobMapper创建定时任务保存到数据库
    3. 设置JobService.execute扫描时间

5. 待完善:

  1. 如果扫描频度范围内可能多次扫描数据库(比如上述方法我设置的是每5秒执行一次扫描,那么在数据表中的人物肯定会被多次扫描),那么任务会被重复创建。可以通过队列解决

  2. 若要支持非基本类型,可以考虑传入Class类型

  3. 反射执行的方法必须是JobService中的方法,传入全类名然后反射即可

现在比较忙,后续会把这个补充完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值