一般情况下,如果想在Spring Boot中使用定时任务,我们只需要@EnableScheduling开启定时任务支持,在需要调度的方法上添加@Scheduled注解。这样就能够在项目中开启定时调度功能了,并且这种方法支持通过cron表达式灵活的控制执行周期和频率。
上述的方式好处是快捷,轻量,缺点是周期一旦指定,想要更改必须要重启应用,如果我们想要动态的对定时任务的执行周期进行变更,甚至动态的增加定时调度任务则上述方式就不适用了。
本文我将讲解如何在Spring 定时任务的基础上进行扩展,实现动态定时任务。
首先介绍的方案1能够实现动态变更已有任务的执行频率/周期。
首先建立一个Spring Boot应用,这里不再展开。
建立一个任务调度类,实现接口SchedulingConfigurer,标记为Spring的一个bean。注意一定要添加注解 @EnableScheduling 开启定时任务支持。
@EnableScheduling @Component public class DynamicCronHandler implements SchedulingConfigurer { private static final String DEFAULT_CRON = "0/5 * * * * ?"; private String taskCron = DEFAULT_CRON; @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { scheduledTaskRegistrar.addTriggerTask(()->{ LOGGER.info("执行任务"); }, triggerContext -> { // 刷新cron CronTrigger cronTrigger = new CronTrigger(taskCron); Date nextExecDate = cronTrigger.nextExecutionTime(triggerContext); return nextExecDate; }); }
scheduledTaskRegistrar.addTriggerTask接受两个参数,分别为需要调度的任务实例(Runnable实例),Trigger实例,这里通过lambda方式注入,需要实现nextExecutionTime回调方法,返回下次执行时间。
通过该回调方法,在Runnable中执行业务逻辑代码,在Trigger修改定时任务的执行周期。
public DynamicCronHandler setTaskImplement(Runnable taskImplement) { this.taskImplement = taskImplement; return this; } public DynamicCronHandler setTaskCron(String taskCron) { this.taskCron = taskCron; return this; } public DynamicCronHandler taskCron(String taskCron) { System.out.println("更新cron=" + taskCron); this.taskCron = taskCron; return this; } ...省略getter... }
编写一个测试类,进行测试:
@RequestMapping("execute") @ResponseBody public String executeTask(@RequestParam(value = "cron", defaultValue = "0/10 * * * * ?") String cron) { LOGGER.info("cron={}", cron); dynamicCronHandler.taskCron(cron); return "success"; }
暴露一个http接口,接受参数cron,启动应用并访问/execute,首次传入参数cron=0/1 ?,表示每秒执行一次任务。日志如下:
2019-12-03 15:32:40.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务 2019-12-03 15:32:41.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务 2019-12-03 15:32:42.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务 2019-12-03 15:32:43.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务 2019-12-03 15:32:44.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务
可以看到每秒执行一次。
更改cron的值为0/5 ?,观察到控制台输出发生变化:
更新cron=0/5 * * * * ? 2019-12-03 15:33:30.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务 2019-12-03 15:33:35.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务 2019-12-03 15:33:40.001 INFO 7196 --- [TaskScheduler-1] c.s.s.t.d.d.DynamicCronHandler : 执行任务
此时定时任务执行频率更新为5秒一次,表明通过SchedulingConfigurer.configureTasks回调,动态的更新了定时任务执行频率。
到目前为止,实现了动态变更定时任务的执行频率,但是不能实现动态的提交定时任务。方案二就是为了解决这个疑问而实现的,
首先建立一个DynamicTaskScheduler类,内容如下:
@Scope(value = "singleton") @Component @EnableScheduling public class DynamicTaskScheduler { private ScheduledFuture<?> future; @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); } public void startCron(Runnable task, String cron) { stopCron(); future = threadPoolTaskScheduler.schedule( task, new CronTrigger(cron) ); } public void stopCron() { if (future != null) { future.cancel(true); System.out.println("stopCron()"); } } }
这里通过startCron提交一个新的任务,通过cron表达式进行调度,在开始之前进行判断是否关闭老的,必须关闭老的才能开启新的。
通过stopCron对老任务进行关闭。
编写一个测试方法测试该动态任务调度类。
@RequestMapping("execute1") @ResponseBody public String executeTask1(@RequestParam(value = "cron", defaultValue = "0/10 * * * * ?") String cron) { LOGGER.info("cron={}", cron); dynamicTaskScheduler.startCron( () -> { LOGGER.info("模拟执行作业,cron={}", cron); }, cron ); return "success"; }
启动方法中初始化一个 ThreadPoolTaskScheduler 实例。
@SpringBootApplication public class SnowalkerTestDemoApplication { public static void main(String[] args) { SpringApplication.run(SnowalkerTestDemoApplication.class, args); } @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(20); executor.setThreadNamePrefix("taskExecutor-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); return executor; } }
运行启动类,访问测试接口/execute1,先传入cron=0/1 ?,表示每秒执行一次任务。日志如下:
更改cron的值为0/5 ?,观察到控制台输出发生变化:
可以看到这种方式同样实现了动态的变更定时任务执行频率,相比上述的方法,该方式更加灵活,能够动态的增加任务到线程池中进行调度,我们可以定义一个Map保存future,从而实现创建并维护多个定时任务,具体可以参考这篇文章 ThreadPoolTaskScheduler的使用,定时任务开启与关闭 ,思路如下:
核心代码如下:
任务存储Map
public static ConcurrentHashMap<String, ScheduledFuture> map = new ConcurrentHashMap<>();
启动任务
@Component @Scope("prototype") public class DynamicTask { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; private ScheduledFuture future; public void startCron() { cron = "0/1 * * * * ?"; System.out.println(Thread.currentThread().getName()); String name = Thread.currentThread().getName(); future = threadPoolTaskScheduler.schedule(new myTask(name), new CronTrigger(cron)); App.map.put(name, future); }
停止任务
public void stop() { if (future != null) { future.cancel(true); } } }
自定义Task定义
public class MyTask implements Runnable { private String name; myTask(String name) { this.name = name; } @Override public void run() { System.out.println("test" + name); } }
测试接口
@Autowired private DynamicTask task; @RequestMapping("/start") public void test() throws Exception { // 开启定时任务,对象注解Scope是多利的。 task.startCron(); } @RequestMapping("/stop") public void stop() throws Exception { // 提前测试用来测试线程1进行对比是否关闭。 ScheduledFuture scheduledFuture = App.map.get("http-nio-8081-exec-2"); scheduledFuture.cancel(true); // 查看任务是否在正常执行之前结束,正常返回true boolean cancelled = scheduledFuture.isCancelled(); while (!cancelled) { scheduledFuture.cancel(true); } }
以上就是SpringBoot动态定时任务相关的讲解,这种方式在轻量级环境下能够很好的工作。如果我们的定时任务要求分布式,高可用,则需要引入额外的组件,如果有必要则需要引入如ejob,xxl-job,quartz等定时调度组件。
版权声明:原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。