停止使用@Scheduled注解启动的任务
子曰:小胜靠智,大胜靠德,常胜靠身体。
1 问题描述
在Spring或Spring Boot应用中如何停止使用@Scheduled
启动的定时任务?
2 解决思路
在Spring中,使用@Scheduled
启动的定时任务,Spring会构造对应的Runnable
或Callable
对象并提交给ThreadPoolTaskScheduler
执行,ThreadPoolTaskScheduler
执行Runnable
或Callable
会返回一个ScheduledFuture
对象,通过ScheduledFuture
对象的cancel()
方法可以实现定时任务的停止。
3 解决方法
3.1 通过ScheduledAnnotationBeanPostProcessor
关闭
ScheduledAnnotationBeanPostProcessor
的postProcessBeforeDestruction
方法可以停止使用@Scheduled
启动的定时任务,该方法主要用于bean被销毁之前停止该bean的所有定时任务。因此,停止任务的另外一个可行方法是销毁bean,此方法过于简单粗暴,不推荐使用。
简单示例代码如下:
@Service
public class ScheduledProcessor implements ApplicationContextAware {
private ApplicationContext context;
private ScheduledAnnotationBeanPostProcessor processor;
@PostConstruct
public void initProcessor() {
processor = (ScheduledAnnotationBeanPostProcessor) context.getBean("org.springframework.context.annotation.internalScheduledAnnotationProcessor");
}
public void stopTask(Object bean) {
processor.postProcessBeforeDestruction(bean, null);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
上面的代码主要是通过ApplicationContext
获取ScheduledAnnotationBeanPostProcessor
并通过postProcessBeforeDestruction
方法取消定时任务。
3.2 通过ScheduledFuture取消任务
通过提供自定义的ThreadPoolTaskScheduler
实现,并覆写ThreadPoolTaskScheduler
执行任务的方法,将返回的ScheduledFuture
的对象和bean的映射关系存储到Map
,然后通过bean获取ScheduledFuture
对象来停止对应的任务。
下面是一段简单的实例代码:
@Service
public class CustomTaskScheduler extends ThreadPoolTaskScheduler {
private final Map<Object, ScheduledFuture<?>> scheduledTasks = new IdentityHashMap<>();
void cancelTask(Object identifier) {
ScheduledFuture future = scheduledTasks.get(identifier);
if (null != future) {
System.out.println("future is not null");
future.cancel(true);
}
}
/**
* call parent method and store the result Future for cancel task,
* you can expand other method of you used.
*
* @param task the task need to be executed
* @param period the time between two continues execute
* @return the result of task
*/
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
ScheduledFuture<?> future = super.scheduleAtFixedRate(task, period);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task;
// Scheduled annotation only can used for no arguments method so hashCode plus method name is unique.
scheduledTasks.put(runnable.getTarget(), future);
return future;
}
/**
* call parent method and store the result Future for cancel task,
* you can expand other method of you used.
*
* @param task the task need to be executed
* @param startTime the task first executed time
* @param period the time between two continues execute
* @return the result of task
*/
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
ScheduledFuture<?> future = super.scheduleAtFixedRate(task, startTime, period);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task;
// Scheduled annotation only can used for no arguments method so hashCode plus method name is unique.
scheduledTasks.put(runnable.getTarget(), future);
return future;
}
}
ScheduledAnnotationBeanPostProcessor只能停止一个bean所有的任务,上面的方法可以精细的控制停止某一个确定的任务,读者可以思考一下如何实现?
总结
在Spring或Spring Boot中取消任务的方法就是通过任务返回的异步结果Future.cancel()实现。可以从这里下载完整的样例代码。