quartz对数据库支持非常强大,但是用起来并没有SpringTask那么简单。 因此,个人造了一点小轮子,让SpringTask拥有类似quartz的功能(当然没有那么完善)。
转载请注明出处,欢迎留言交流。
设计思路: exec_time字段提供对固定时间执行一次的支持,也可以通过cron字段,实现任意触发时间。
method_name字段表示需要触发的方法名; args则是method_name对应方法的参数值; args_type则是args的具体类型(暂时仅支持基本数据类型以及包装类)。
注入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);
}
}
现在比较忙,后续会把这个补充完成。