之前是这样写的,每次要改定时器都要修改发版,很麻烦:
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容器中的类名,要和数据库中的数据保存一致!
完毕!