如何停止使用@Scheduled注释启动的计划任务?

我使用Spring Framework的@Scheduled注释创建了一个简单的计划任务。

  @Scheduled(fixedRate = 2000) public void doSomething() {} 

现在我想在不再需要时停止这项任务。

我知道在这个方法的开头可以有一个替代方法来检查一个条件标志,但是这不会停止执行这个方法。

是否有Spring提供什么来阻止@Scheduled任务?

选项1:使用后处理器

提供ScheduledAnnotationBeanPostProcessor并为应该停止其调度的bean显式调用postProcessBeforeDestruction(Object bean, String beanName)

选项2:将目标bean的地图维护到其Future

 private final Map> scheduledTasks = new IdentityHashMap<>(); @Scheduled(fixedRate = 2000) public void fixedRateJob() { System.out.println("Something to be done every 2 secs"); } @Bean public TaskScheduler poolScheduler() { return new CustomTaskScheduler(); } class CustomTaskScheduler extends ThreadPoolTaskScheduler { @Override public ScheduledFuture scheduleAtFixedRate(Runnable task, long period) { ScheduledFuture future = super.scheduleAtFixedRate(task, period); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task; scheduledTasks.put(runnable.getTarget(), future); return future; } @Override public ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period) { ScheduledFuture future = super.scheduleAtFixedRate(task, startTime, period); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task; scheduledTasks.put(runnable.getTarget(), future); return future; } } 

当必须停止bean的调度时,您可以查找映射以获取相应的Future并显式取消它。

这个问题有点含糊不清

  1. 当你说“停止这个任务”时,你的意思是停止以后它可以恢复(如果是,以编程方式,使用在同一个应用程序中出现的条件?或外部条件?)
  2. 您是否在同一上下文中运行任何其他任务? (关闭整个应用程序而不是任务的可能性) – 您可以在此场景中使用actuator.shutdown端点

我最好的猜测是,您希望使用可能以可恢复的方式在同一个应用程序中出现的条件来关闭任务。 我会尝试根据这个假设回答。

这是我能想到的最简单的解决方案,但是我会做一些改进,比如早期返回而不是嵌套

 @Component public class SomeScheduledJob implements Job { private static final Logger LOGGER = LoggerFactory.getLogger(SomeScheduledJob.class); @Value("${jobs.mediafiles.imagesPurgeJob.enable}") private boolean imagesPurgeJobEnable; @Override @Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}") public void execute() { if(!imagesPurgeJobEnable){ return; } Do your conditional job here... } 

上述代码的属性

 jobs.mediafiles.imagesPurgeJob.enable=true or false jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ? 

前段时间我在我的项目中有这个要求,任何组件都应该能够创建一个新的计划任务或停止调度程序(所有任务)。 所以我做了这样的事

 @Configuration @EnableScheduling @ComponentScan @Component public class CentralScheduler { private static AnnotationConfigApplicationContext CONTEXT = null; @Autowired private ThreadPoolTaskScheduler scheduler; public static CentralScheduler getInstance() { if (!isValidBean()) { CONTEXT = new AnnotationConfigApplicationContext(CentralScheduler.class); } return CONTEXT.getBean(CentralScheduler.class); } @Bean public ThreadPoolTaskScheduler taskScheduler() { return new ThreadPoolTaskScheduler(); } public void start(Runnable task, String scheduleExpression) throws Exception { scheduler.schedule(task, new CronTrigger(scheduleExpression)); } public void start(Runnable task, Long delay) throws Exception { scheduler.scheduleWithFixedDelay(task, delay); } public void stopAll() { scheduler.shutdown(); CONTEXT.close(); } private static boolean isValidBean() { if (CONTEXT == null || !CONTEXT.isActive()) { return false; } try { CONTEXT.getBean(CentralScheduler.class); } catch (NoSuchBeanDefinitionException ex) { return false; } return true; } } 

所以我可以这样做

 Runnable task = new MyTask(); CentralScheduler.getInstance().start(task, 30_000L); CentralScheduler.getInstance().stopAll(); 

请记住,出于某些原因,我不必担心并发性。 否则应该有一些同步。

这是一个示例,我们可以停止,启动和列出所有计划的运行任务:

 @RestController @RequestMapping("/test") public class TestController { private static final String SCHEDULED_TASKS = "scheduledTasks"; @Autowired private ScheduledAnnotationBeanPostProcessor postProcessor; @Autowired private ScheduledTasks scheduledTasks; @Autowired private ObjectMapper objectMapper; @GetMapping(value = "/stopScheduler") public String stopSchedule(){ postProcessor.postProcessBeforeDestruction(scheduledTasks, SCHEDULED_TASKS); return "OK"; } @GetMapping(value = "/startScheduler") public String startSchedule(){ postProcessor.postProcessAfterInitialization(scheduledTasks, SCHEDULED_TASKS); return "OK"; } @GetMapping(value = "/listScheduler") public String listSchedules() throws JsonProcessingException{ Set setTasks = postProcessor.getScheduledTasks(); if(!setTasks.isEmpty()){ return objectMapper.writeValueAsString(setTasks); }else{ return "No running tasks !"; } } 

}

计划

当spring进程Scheduled ,它将迭代每个注释此注释的方法并按bean组织任务,如下面的源所示:

 private final Map> scheduledTasks = new IdentityHashMap>(16); 

取消

如果您只想取消重复的计划任务,您可以执行以下操作(这是我的仓库中的可运行演示 ):

 @Autowired private ScheduledAnnotationBeanPostProcessor postProcessor; @Autowired private TestSchedule testSchedule; public void later() { postProcessor.postProcessBeforeDestruction(test, "testSchedule"); } 

注意

它会找到这个bean的ScheduledTask并逐个取消它。 应该注意的是它还将停止当前运行的方法(如postProcessBeforeDestruction源显示)。

  synchronized (this.scheduledTasks) { tasks = this.scheduledTasks.remove(bean); // remove from future running } if (tasks != null) { for (ScheduledTask task : tasks) { task.cancel(); // cancel current running method } }