Spring Schedule如何动态添加修改定时任务
Spring Schedule如何动态添加修改定时任务
1、快速开始
通常情况下,我们使用的功能很简单,只需要在配置类上加一个@EnableScheduling
注解,然后在Bean对应的方法上添加@Scheduled
注解即可。但一般情况下,还会自定义对应的线程池等信息,如下所示。
@EnableScheduling
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = threadPoolTaskScheduler();
taskRegistrar.setScheduler(taskScheduler);
}
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1);
scheduler.setThreadNamePrefix("scheduler-");
scheduler.setRemoveOnCancelPolicy(true);
return scheduler;
}
}
@Scheduled
注解通常有三种调度方式,fixedRate
、fixedDelay
和cron
。
fixedRate
:定时执行,如@Scheduled(fixedRate= 2000)
会每隔两秒执行一次fixedDelay
:固定延迟,如@Scheduled(fixedDelay= 2000)
会在上次任务执行完成后,延迟两秒再触发下一次cron
:自定义规则,比较复杂,功能更强大
2、Schedule的动态修改
以cron表达式任务为例,在上面的基础上,如果有如下定时任务,在每天凌晨一点执行一次,但是后面发现时间不合适,需要修改触发时间为凌晨两点,按照现有的方式,通常只能修改代码重新部署了。
@Scheduled(cron = "0 0 1 * * ?")
public void foo() {
// do something
}
可能有人会问,为啥不用Quartz?Quartz自然是非常方便强大的,但不是本篇要讲的内容,本篇就偏要使用SpringSchedule来实现动态的cron表达式任务。
在上面的快速开始一节中,通过configureTasks
,我们可以拿到ScheduledTaskRegistrar
实例,在这个实例中提供了很多的操作定时任务方法
public ScheduledTask scheduleTriggerTask(TriggerTask task) {/**/}
public ScheduledTask scheduleCronTask(CronTask task) {/**/}
public ScheduledTask scheduleFixedRateTask(IntervalTask task) {/**/}
public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {/**/}
public ScheduledTask scheduleFixedDelayTask(IntervalTask task) {/**/}
public ScheduledTask scheduleFixedDelayTask(FixedDelayTask task) {/**/}
修改第一步中的配置如下,为了操作简单,这里直接通过ApplicationRunner
来进行测试
@Slf4j
@EnableScheduling
@Configuration
public class SchedulerConfig implements SchedulingConfigurer, ApplicationRunner {
private ScheduledTaskRegistrar taskRegistrar;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = threadPoolTaskScheduler();
taskRegistrar.setScheduler(taskScheduler);
this.taskRegistrar = taskRegistrar;
}
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1);
scheduler.setThreadNamePrefix("scheduler-");
scheduler.setRemoveOnCancelPolicy(true);
return scheduler;
}
@Override
public void run(ApplicationArguments args) throws Exception {
CronTask cronTask = new CronTask(() -> log.info("foo-----------"), "0/2 * * * * ?");
ScheduledTask fooTask = taskRegistrar.scheduleCronTask(cronTask);
ExecutorService executor = Executors.newSingleThreadExecutor(Thread::new);
executor.execute(() -> {
try {
// 等10秒
TimeUnit.SECONDS.sleep(10);
Runnable runnable = fooTask.getTask().getRunnable();
// 停止foo任务
fooTask.cancel();
// 重新添加,并修改触发时间为每3秒一次
taskRegistrar.scheduleCronTask(new CronTask(runnable, "0/3 * * * * ?"));
// 再添加一个bar任务,每秒执行一次
taskRegistrar.scheduleCronTask(new CronTask(() -> log.info("bar..."), "0/1 * * * * ?"));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
日志如下,从日志中可以看到,上面的操作是成功的,而且也是非常方便的,可以很方便的动态添加定时任务,其余几个方法就不写出来了,感兴趣的可以自己试一下。
2023-04-28 16:38:21.547 INFO 8592 --- [ main] com.example.SpringBootQuartzApplication : Started SpringBootQuartzApplication in 1.101 seconds (JVM running for 1.563)
2023-04-28 16:38:22.002 INFO 8592 --- [ scheduler-1] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:24.001 INFO 8592 --- [ scheduler-1] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:26.001 INFO 8592 --- [ scheduler-2] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:28.001 INFO 8592 --- [ scheduler-1] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:30.002 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:32.002 INFO 8592 --- [ scheduler-1] com.example.task.SchedulerConfig : bar
2023-04-28 16:38:33.000 INFO 8592 --- [ scheduler-2] com.example.task.SchedulerConfig : bar
2023-04-28 16:38:33.000 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:34.002 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : bar
2023-04-28 16:38:35.001 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : bar
2023-04-28 16:38:36.001 INFO 8592 --- [ scheduler-1] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:36.001 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : bar
2023-04-28 16:38:37.002 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : bar
2023-04-28 16:38:38.002 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : bar
2023-04-28 16:38:39.001 INFO 8592 --- [ scheduler-1] com.example.task.SchedulerConfig : foo-----------
2023-04-28 16:38:39.001 INFO 8592 --- [ scheduler-3] com.example.task.SchedulerConfig : bar
当然,上面的例子中,因为都是在run方法内,所以没那么多讲究,一般在正式使用的时候,会在scheduleXXXTask
返回的ScheduledTask
实例保存起来,比如保存到map中并给一个唯一key之类的,以方便后续操作,又或者自定义类实现Runable接口并在其中指定能唯一标识这个任务的方法。具体如何实现,就看具体场景了。
quartz以及xxl-job等框架也是非常优秀的任务调度框架,提供的功能更为强大,但对于比较简单的小项目来说,没有引入的必要,Spring Schedule已经足够用了。