如何把Java的定时任务写到数据库里面去配置?

文章描述了从使用硬编码定时器到将定时任务配置迁移到数据库的过程,使用Spring框架的`@Scheduled`和`TaskScheduler`,以及如何通过Cron表达式进行周期性任务调度。现在,定时器配置的灵活性和管理得到了显著提升。
摘要由CSDN通过智能技术生成

之前是这样写的,每次要改定时器都要修改发版,很麻烦:

package cn.net.cdsz.ccb.common.scheduled;

import cn.net.cdsz.ccb.business.config.Custom;
import cn.net.cdsz.ccb.business.service.CCBBankService;
import cn.net.cdsz.ccb.business.service.CCBTestSetService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * 这个是每6小时(程序启动)执行一次的   
 */
@Component
@EnableAsync
public class correctMoney {
    @Autowired
    private Custom custom  ;

    private Logger logger = LogManager.getLogger();

    @Autowired
    private CCBTestSetService cCBTestSetService;

    //@Scheduled(fixedRate = 60000)   // 60000每隔一分钟执行一次
    @Scheduled(cron = " 0 0 0 * * ?") // 每天凌晨执行一次 ,专业  [秒] [分] [小时] [日] [月] [周] [年]
    //@Scheduled(cron = " 0 * * * * ?")//这样是每3秒执行一次了,专业  [秒] [分] [小时] [日] [月] [周] [年]
    public void run() {

        if(custom.getIsscheduled()){

            try {
                run(()-> {
                   cCBTestSetService.AutomaticDeductionByDay(); 
                });
            }catch (RuntimeException e){
                logger.error(e.getMessage());
            }catch (Exception e){
                logger.error(e);
            }

        }

    }

    public void run(Runnable runnable) {
        runnable.run();
    }

}

现在改成数据库里面去配置定时器了,就容易了很多,上代码:

package cn.net.cdsz.ccb.common.scheduled;

import club.newepoch.utils.JsonUtils;
import club.newepoch.utils.StringUtils;
import cn.net.cdsz.ccb.business.model.pojo.ScheduledTask;
import cn.net.cdsz.ccb.business.model.pojo.ScheduledTaskLog;
import cn.net.cdsz.ccb.common.bean.BaseHolder;
import cn.net.cdsz.ccb.common.event.GenTables;
import lombok.SneakyThrows;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * 动态调度器(配置写数据库)
 */
@Service
public class DynamicScheduler {
    final private TaskScheduler taskScheduler;
    final private  GenTables genTables;
    private Map<Long, ScheduledFuture<?>> jobsMap = new ConcurrentHashMap<>();
    private Map<Long, String> taskCronMap = new ConcurrentHashMap<>();//自己维护一个调度时间的表
    public DynamicScheduler(TaskScheduler taskScheduler, GenTables genTables) {
        this.genTables = genTables;
        this.taskScheduler = taskScheduler;
    }

    // 这个方法用来启动所有的active任务
    //@PostConstruct
    public void startActiveJobs() {
        ScheduledTask scheduledTaskSql = new ScheduledTask();
        List<ScheduledTask> tasks = genTables.queryMore(scheduledTaskSql);
        tasks.forEach(this::scheduleTask);
    }

    // 用于调度任务
    public void scheduleTask(ScheduledTask task) {
        if ("1".equals(task.getIsActive())) {
            ScheduledFuture<?> scheduledTask = taskScheduler.schedule(
                    () -> runTask(task),
                    new CronTrigger(task.getCronExpression(), TimeZone.getTimeZone(TimeZone.getDefault().getID()))
            );
            jobsMap.put(task.getKeyId(), scheduledTask);
            taskCronMap.put(task.getKeyId(), task.getCronExpression());
        } else {
            cancelTask(task.getKeyId());
        }
    }

    // 用于取消计划中的任务
    public void cancelTask(Long taskId) {
        ScheduledFuture<?> scheduledTask = jobsMap.get(taskId);
        if (scheduledTask != null) {
            scheduledTask.cancel(true);
            jobsMap.remove(taskId);
            taskCronMap.remove(taskId);
        }
    }
    @SneakyThrows
    // 调用转换函数,将字符串值转换为对应的对象类型
    private Object convertStringToObject(String value, Class<?> type) {
        if (String.class == type) {
            return value;
        } else if (Integer.class == type || int.class == type) {
            return Integer.valueOf(value);
        } else if (Double.class == type || double.class == type) {
            return Double.valueOf(value);
        }
        // 可以根据需要添加更多类型的转换
        throw new IllegalArgumentException("Unsupported type: " + type);
    }

    private void runTask(ScheduledTask task) {
        // 这里执行你的任务逻辑9 15
        Object bean = BaseHolder.getBean(task.getBeanStr());    // 获取bean实例
        String methodName = task.getExecMath();          // 从数据库获取的方法名
        String paramTypeNamesStr = task.getParamTypeNamesStr(); // 从数据库获取的参数类型名字符串
        String paramValuesStr = task.getParamValuesStr();    // 从数据库获取的参数值字符串
        // 使用.split(", ")方法来分割字符串并转换为数组,然后将数组转换为列表
        List<String> paramTypeNames = Arrays.asList(paramTypeNamesStr.split(","));  // 方法参数类型
        List<String> paramValues = Arrays.asList(paramValuesStr.split(","));// 方法参数值

        // 将字符串类型名称转换为Class类型对象
        Class<?>[] parameterTypes = new Class<?>[paramTypeNames.size()];
        for (int i = 0; i < paramTypeNames.size(); i++) {
            try {
                if(StringUtils.isBlank(paramTypeNames.get(i))){
                    continue;
                }
                parameterTypes[i] = Class.forName(paramTypeNames.get(i));
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        // 将字符串参数值转换为相应的对象
        Object[] parameters = new Object[paramValues.size()];
        for (int i = 0; i < paramValues.size(); i++) {
            if(StringUtils.isBlank(paramValues.get(i))){
                continue;
            }
            String value = paramValues.get(i);
            Class<?> type = parameterTypes[i];
            // 调用转换函数,将字符串值转换为对应的对象类型
            parameters[i] = convertStringToObject(value, type);
        }
        // 写日志。。。
        ScheduledTaskLog  scheduledTaskLog = new ScheduledTaskLog();
        scheduledTaskLog.setTaskName(task.getTaskName());
        scheduledTaskLog.setCronExpression(task.getCronExpression());
        scheduledTaskLog.setBeanStr(task.getBeanStr());
        scheduledTaskLog.setExecMath(task.getExecMath());
        scheduledTaskLog.setParamTypeNamesStr(task.getParamTypeNamesStr());
        scheduledTaskLog.setParamValuesStr(task.getParamValuesStr());
        // ... 接下来是通过反射调用方法的过程 ...
        try {
            Method method;
            // 判断是否有参数类型存在
            if ((parameterTypes == null || parameterTypes.length == 0) || (parameters == null || Arrays.stream(parameters).allMatch(Objects::isNull))) {
                // 如果没有参数类型则认为是不带参数的方法
                method = bean.getClass().getMethod(methodName);
                // 使用.invoke()调用方法,传入bean和参数数组
                Object result = method.invoke(bean);
                scheduledTaskLog.setResultStr(JsonUtils.toJSONString(result));
            } else {
                // 获取具有指定参数类型的方法对象
                method = bean.getClass().getMethod(methodName, parameterTypes);
                // 使用.invoke()调用方法,传入bean和参数数组
                Object result = method.invoke(bean, parameters);
                scheduledTaskLog.setResultStr(JsonUtils.toJSONString(result));
            }
            //保存调度的执行日志
            genTables.save(scheduledTaskLog);
            // 处理调用结果
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            // 处理没有找到具有指定参数类型的方法的情况
        } catch (Exception e) {
            e.printStackTrace();
            // 处理其他可能的异常
        }
    }

    // 你可以通过定时任务,周期性地从数据库中获取最新的任务配置
    @Scheduled(fixedRate = 1000*60) //单位是毫秒(ms)
    public void refreshActiveJobs() {
        // 查询数据库中所有active的任务并更新调度
        ScheduledTask scheduledTaskSql = new ScheduledTask();
        List<ScheduledTask> tasks = genTables.queryMore(scheduledTaskSql);

        for (int i = 0; i < tasks.size(); i++) {
            ScheduledTask task = tasks.get(i);
            if (!jobsMap.containsKey(task.getKeyId())) {
                // 如果在内存中不存在,则为新任务,需要调度
                scheduleTask(task);
            } else {
                // 如果已经存在,检查cron表达式是否更新
                String cronStr = taskCronMap.get(task.getKeyId());
                // 如果内容不一样,那么就修改这个计划
                if(!cronStr.equals(task.getCronExpression())){
                    // Cron表达式已更改,重新调度
                    cancelTask(task.getKeyId());
                    scheduleTask(task);
                }
            }
        }
        // 取消已经被设置为非active的任务
        for (Map.Entry<Long, ScheduledFuture<?>> entry : jobsMap.entrySet()) {
            Long taskId = entry.getKey();
            ScheduledTask scheduledTaskSql2 = new ScheduledTask();
            scheduledTaskSql2.setKeyId(taskId);
            ScheduledTask scheduledTask = genTables.queryOne(scheduledTaskSql2);
            if (scheduledTask !=null && "0".equals(scheduledTask.getIsActive())) {
                cancelTask(taskId);
            }
        }

    }
}

上一个线程的辅助类:

    package cn.net.cdsz.ccb.common.config.app;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class AppConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        // 设定线程池大小,可以根据实际情况调整
        scheduler.setPoolSize(5);
        // 设置线程名称前缀
        scheduler.setThreadNamePrefix("TaskScheduler-");
        // 线程池关闭前的最大等待时间,确保所有任务都能完成
        scheduler.setAwaitTerminationSeconds(600);
        // 设置当调度器shutdown被调用时等待当前被调度的任务完成
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        // 初始化线程池
        scheduler.initialize();
        return scheduler;
    }
}

上数据库表的sql:

CREATE TABLE `scheduled_task` (
  `key_id` bigint(19) NOT NULL AUTO_INCREMENT,
  `task_name` varchar(255) NOT NULL DEFAULT '' COMMENT '任务名字',
  `cron_expression` varchar(255) NOT NULL DEFAULT '' COMMENT '周期配置比如:0 30 0 * * ?',
  `is_active` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否有效',
  `bean_str` varchar(255) NOT NULL DEFAULT '' COMMENT '获取bean实例',
  `exec_math` varchar(255) NOT NULL DEFAULT '' COMMENT '从数据库获取的方法名,写一个',
  `param_type_names_str` varchar(255) NOT NULL DEFAULT '' COMMENT '从数据库获取的参数类型名字符串,用英文,隔开',
  `param_values_str` varchar(255) NOT NULL DEFAULT '' COMMENT '从数据库获取的参数值字符串,用英文,隔开',
  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `lived` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`key_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='动态调度(手动配置的定时器)';




CREATE TABLE `scheduled_task_log` (
  `key_id` bigint(19) NOT NULL AUTO_INCREMENT,
  `task_name` varchar(255) NOT NULL DEFAULT '',
  `cron_expression` varchar(255) NOT NULL DEFAULT '',
  `bean_str` varchar(255) NOT NULL DEFAULT '',
  `exec_math` varchar(255) NOT NULL DEFAULT '',
  `param_type_names_str` varchar(255) NOT NULL DEFAULT '',
  `param_values_str` varchar(255) NOT NULL DEFAULT '',
  `result_str` text,
  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `lived` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`key_id`)
) ENGINE=InnoDB AUTO_INCREMENT=36625 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

上截图:

注意:把这个加上,固定好类在spring容器中的类名,要和数据库中的数据保存一致!

完毕!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值