Springboot 通过自带的@Scheduled 实现定时任务
2019-05-09
阅读 {{counts.readCount}}
评论 {{counts.commentCount}}
## 需求
项目中需要用到一些简单的定时任务,定时执行一些事先写好的java方法
但具体何时执行等,需要做到数据库,实现动态配置
最终选择采用Springboot自带的方案 `@Schedule` 或 `SchedulingConfigurer` 来实现
## 参考
参考以下文章
https://www.cnblogs.com/mmzs/p/10161936.html
https://www.cnblogs.com/zt007/p/8954096.html
## 数据库

## 代码
**主要依赖** `Springboot2` `MybatisPLus`
<br>
定时任务方法类
`Job.java`
```java
/**
* 定时任务
* zmh
* 2019-5-7 13:54:08
*/
public class Job {
public void test5(String str){
System.out.println("定时任务 5秒执行一次 参数:" + str);
}
public void test12(){
System.out.println("定时任务 12点执行一次 无参数");
}
}
```
<br>
配置定时任务
`ScheduleConfig.java`
```java
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xxx.xxx.entity.QuarztJob;
import com.xxx.xxx.service.IQuarztJobService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* zzzmh
* 2019-5-6 16:29:56
* 动态定时任务
*/
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Autowired
private IQuarztJobService quarztJobService;
/**
* 注册定时任务
*
* 程序启动先获取所有任务id,并逐一查询出最新状态和执行时间
* 如果状态status = 1说明是暂停中,跳过执行,但依旧注册
* 每次执行前查看数据库最新的任务状态 任务目标和参数
* 每次执行后查看数据库注册最新的下次执行时间
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
QueryWrapper<QuarztJob> wrapper = new QueryWrapper<>();
// 查询所有任务id (这一步先不管 status)
List<Map<String, Object>> list = quarztJobService.listMaps(wrapper.select("id"));
// 根据数据库配置实时触发
for (Map<String, Object> map : list) {
int id = (int) map.get("id");
taskRegistrar.addTriggerTask(
// 添加任务内容(Runnable)
() -> {
// 从数据库获取执行任务 每次要确保拿最新的
QuarztJob job = quarztJobService.getById(id);
// 如果 status 是1 代表暂停 跳过任务
if (job.getStatus().intValue() == 0) {
try {
// 这里通过反射调用方法
// 包名根据实际Bean所在位置调整
String pack = "com.xxx.xxx.quarztJob.";
Class<?> obj = Class.forName(pack + job.getBeanName());
// 考虑程序复杂度,简化为根据数据库参数字段是否为空 要么无参数 要么单String类型参数
String params = job.getParams();
if (StringUtils.isBlank(params)) {
Method method = obj.getMethod(job.getMethodName());
// 每次new一个新对象
method.invoke(obj.newInstance());
} else {
Method method = obj.getMethod(job.getMethodName(), String.class);
// 每次new一个新对象
method.invoke(obj.newInstance(), params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
},
// 设置执行周期(Trigger)
triggerContext -> {
// 从数据库获取执行周期 每次要确保拿最新的
QuarztJob job = quarztJobService.getById(id);
String cron = job.getCronExpression();
// 返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}
}
}
```
## 运行结果

需要注意:由于用了反射方法,定时任务数据库和实体类之间的配置必须一一对应,否则会一触发就报错!
另外直接在Job中使用@Autowired 存在无法注入Service问题
目前我用的解决方案是从注册定时任务的地方传参
## END
欢迎关注
个人博客: https://zzzmh.cn
学习笔记: https://leanote.zzzmh.cn
极简壁纸: https://bz.zzzmh.cn
极简插件: https://chrome.zzzmh.cn