需求
在项目中直接使用了Spring 提供的 @Scheduled
注解来实现定时任务的配置。然后希望能够根据不同的环境去配置不同的定时任务配置。
实现
先要看这个不同的环境的配置是来自哪里?可以是配置文件,也可以是数据库。
配置来自文件
如果是配置文件就比较简单,可以直接使用 ${ properties:default }
或者 #{ bean.properties?:defualt }
的 SpEL 表达式,来直接获取配置文件中的属性。还可以设置默认值。
配置文件中配置属性
# 在不同的profile文件下去声明配置
scheduled:
cron:
task1: 0 0 4 * * ?
task2: 0 0/1 * * * ?
代码中如何去接收属性
/**
* 定时任务-直接获取配置属性
*/
@Scheduled(cron = "${scheduled.cron.task1:0 0/1 * * * ?}")
void scheduling() {
// 定时任务执行的内容
}
---
// 使用一个类,去接受配置文件属性的映射
/**
* 定时任务的cron表达式配置映射类,可以统一管理默认值。或者配置初始化的代码逻辑
*/
@Configuration
@ConfigurationProperties(prefix = "scheduled.cron")
public class CronConfig {
private String task1 = "这里可以设置属性的默认值";
// ...
}
/**
* 定时任务-间接获取配置属性
* - cronConfig 一定是要在容器中的名字
* - getTask1() 方法一定是public的!
*/
@Scheduled(cron = "#{cronConfig.getTask1()}")
void scheduling() {
// 定时任务执行的内容
}
这样在不同环境下,只需去 application-profile.yml
修改或创建想要设置的属性即可。然后运行时,执行想要加载的环境配置即可。
配置来自数据库(或其它)
这种情况,一般我们只能在项目启动时,与数据库建立连接后去拉取数据库中的配置信息,然后通过编程的方式去动态的创建调度任务。
首先要想通过变成的方式,去动态的设置调度任务的配置。那么我们必须先实现它暴露出来的一个接口 SchedulingConfigurer
它只有一个方法 void configureTasks(ScheduledTaskRegistrar taskRegistrar)
它将调度任务的注册器给暴露出来。这样我们可以通过注册器,去注册我们想要注册的任务(从某种程度上来说,也是替代了注解方式)。
这里先看核心的配置类 SchedulingTaskConfigure
:
/**
* 调度任务的自定义配置类
*/
@Configuration
class SchedulingTaskConfigure implements SchedulingConfigurer, SchedulingTaskRegistrar {
// 存储需要注册的任务列表。当 ScheduledTaskRegistrar 还没有加载进来时,需要注册的任务先暂时放入这里。
private final List<SchedulingTask> schedulingTaskList;
// 将注册器保留下来
private volatile ScheduledTaskRegistrar taskRegistrar;
public SchedulingTaskConfigure() {
// 初始化任务列表
this.schedulingTaskList = new ArrayList<>();
}
// 当项目启动后,开启`@EnableScheduling` 注解。然后Spring扫描到 SchedulingConfigurer 有实现类时就会调用这里
// 这个方法只会调用一次
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
System.out.println("configureTasks is init!");
this.taskRegistrar = taskRegistrar;
// fixme 可以支持设置线程池
taskRegistrar.setScheduler(taskScheduler());
System.out.println("configureTasks is init! = " + this.schedulingTaskList);
// 这里是循环去任务列表注册任务
for (SchedulingTask schedulingTask : schedulingTaskList) {
taskRegistrar.addTriggerTask(
// 执行定时任务
() -> {
System.out.println("configureTasks is running! 执行定时任务");
schedulingTask.taskService();
},
// 设置触发器
triggerContext -> {
System.out.println("configureTasks is running! 设置触发器");
// 获取定时任务周期表达式
final String cron = schedulingTask.cron();
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
);
}
}
// 这里其它代码,如果需要定时任务的支持时,就调用这个方法并传递任务的实例
@Override
public void add(SchedulingTask schedulingTask) {
System.out.println("this.schedulingTaskList = " + this.schedulingTaskList + ", taskRegistrar = " + this.taskRegistrar);
if (this.taskRegistrar != null) {
this.taskRegistrar.addCronTask(schedulingTask::taskService, schedulingTask.cron());
return;
}
this.schedulingTaskList.add(schedulingTask);
}
// 这里支持原生线程池,也支持重新@Bean-spring-schedule的线程池
private Executor taskScheduler() {
// 设置线程名称。这里使用了guava的库(非必需)
final ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
// 创建线程池
return Executors.newScheduledThreadPool(5, namedThreadFactory);
}
}
上面的就是我们自定义的调度配置类。其中我们还额外需要两个类 SchedulingTaskRegistrar
和 SchedulingTask
。前者是对外提供注册定时任务的接口。后者则是任务的实例类。
/**
* 对外暴露的定时任务注册接口
*/
public interface SchedulingTaskRegistrar {
// 注册定时任务
void add(SchedulingTask schedulingTask);
}
/**
* 声明任务的实例类
*/
public interface SchedulingTask {
/**
* 执行定时任务
*/
void taskService();
/**
* cron表达式
*/
String cron();
}
上述的三个类,就可以帮助我们将数据库中的配置的数据,转为对应的定时任务。在需要的地方去注入 SchedulingTaskRegistrar
即可,然后调用add方法。
PS:这种方式甚至可以针对一些管理后台的项目。比如运营人员动态设置定时任务,检查定时任务的状态,定时任务的开启/关闭等等,都可以通过自定义调度的配置来实现。