之前是这样写的,每次要改定时器都要修改发版,很麻烦:
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 {@Autowiredprivate Custom custom ;private Logger logger = LogManager.getLogger();@Autowiredprivate 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任务//@PostConstructpublic 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 15Object 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 {@Beanpublic 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容器中的类名,要和数据库中的数据保存一致!
完毕!