[英]How to stop a scheduled task that was started using @Scheduled annotation?
I have created a simple scheduled task using Spring Framework's @Scheduled
annotation.我使用 Spring Framework 的
@Scheduled
注释创建了一个简单的计划任务。
@Scheduled(fixedRate = 2000)
public void doSomething() {}
Now I want to stop this task, when no longer needed.现在我想在不再需要时停止这项任务。
I know there could be one alternative to check one conditional flag at the start of this method, but this will not stop execution of this method.我知道可能有一种替代方法可以在此方法开始时检查一个条件标志,但这不会停止执行此方法。
Is there anything Spring provides to stop @Scheduled
task? Spring 是否提供了停止
@Scheduled
任务的功能?
Supply ScheduledAnnotationBeanPostProcessor
and explicitly invoke postProcessBeforeDestruction(Object bean, String beanName)
, for the bean whose scheduling should be stopped.为应停止调度的 bean 提供
ScheduledAnnotationBeanPostProcessor
并显式调用postProcessBeforeDestruction(Object bean, String beanName)
。
private final Map<Object, ScheduledFuture<?>> 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;
}
}
When the scheduling for a bean has to be stopped, you can lookup the map to get the corresponding Future
to it and explicitly cancel it.当必须停止 bean 的调度时,您可以查找映射以获取对应的
Future
并显式取消它。
Here is an example where we can stop , start , and list also all the scheduled running tasks:这是一个示例,我们可以在其中停止、启动和列出所有计划运行的任务:
@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<ScheduledTask> setTasks = postProcessor.getScheduledTasks();
if(!setTasks.isEmpty()){
return objectMapper.writeValueAsString(setTasks);
}else{
return "No running tasks !";
}
}
}
Some time ago I had this requirement in my project that any component should be able to create a new scheduled task or to stop the scheduler (all tasks).前段时间我在我的项目中有这样一个要求,即任何组件都应该能够创建新的计划任务或停止计划程序(所有任务)。 So I did something like this
所以我做了这样的事情
@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;
}
}
So I can do things like所以我可以做这样的事情
Runnable task = new MyTask();
CentralScheduler.getInstance().start(task, 30_000L);
CentralScheduler.getInstance().stopAll();
Have in mind that, for some reasons, I did it without having to worry about concurrency.请记住,出于某些原因,我这样做时不必担心并发性。 There should be some synchronization otherwise.
否则应该有一些同步。
There is a bit of ambiguity in this question这个问题有点含糊
My best guess is, you are looking to shutdown a task using a condition that may arise with in the same app, in a recoverable fashion.我最好的猜测是,您希望以可恢复的方式使用同一应用程序中可能出现的条件关闭任务。 I will try to answer based on this assumption.
我将尝试根据这个假设来回答。
This is the simplest possible solution that I can think of, However I will make some improvements like early return rather than nested if s这是我能想到的最简单的解决方案,但是我会做一些改进,比如提前返回而不是嵌套 if s
@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...
}
Properties for the above code上述代码的属性
jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?
A working example implementation of @Mahesh 's Option 1, using ScheduledAnnotationBeanPostProcessor.postProcessBeforeDestruction(bean, beanName)
. @Mahesh 选项 1 的工作示例实现,使用
ScheduledAnnotationBeanPostProcessor.postProcessBeforeDestruction(bean, beanName)
。
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
public class ScheduledTaskExample implements ApplicationContextAware, BeanNameAware
{
private ApplicationContext applicationContext;
private String beanName;
@Scheduled(fixedDelay = 1000)
public void someTask()
{
/* Do stuff */
if (stopScheduledTaskCondition)
{
stopScheduledTask();
}
}
private void stopScheduledTask()
{
ScheduledAnnotationBeanPostProcessor bean = applicationContext.getBean(ScheduledAnnotationBeanPostProcessor.class);
bean.postProcessBeforeDestruction(this, beanName);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
{
this.applicationContext = applicationContext;
}
@Override
public void setBeanName(String beanName)
{
this.beanName = beanName;
}
}
Another approach that I have not found yet.我还没有找到的另一种方法。 Simple, clear and thread safe.
简单、清晰且线程安全。
In your configuration class add annotation:在您的配置类中添加注释:
@EnableScheduling
This and next step in your class where you need start/stop scheduled task inject:这一步和你需要开始/停止计划任务注入的课程的下一步:
@Autowired TaskScheduler taskScheduler;
Set fields:设置字段:
private ScheduledFuture yourTaskState; private long fixedRate = 1000L;
Create inner class that execute scheduled tasks eg.:创建执行计划任务的内部类,例如:
class ScheduledTaskExecutor implements Runnable{ @Override public void run() { // task to be executed } }
Add start() method:添加 start() 方法:
public void start(){ yourTaskState = taskScheduler.scheduleAtFixedRate(new ScheduledTaskExecutor(), fixedRate); }
Add stop() method:添加 stop() 方法:
public void stop(){ yourTaskState.cancel(false); }
TaskScheduler provide other common way for scheduling like: cron or delay. TaskScheduler 提供了其他常用的调度方式,如:cron 或延迟。
ScheduledFuture provide also isCancelled();
ScheduledFuture 还提供
isCancelled();
When spring process Scheduled
, it will iterate each method annotated this annotation and organize tasks by beans as the following source shows:当 spring 处理
Scheduled
,它将迭代每个注释该注释的方法并按 bean 组织任务,如下源所示:
private final Map<Object, Set<ScheduledTask>> scheduledTasks =
new IdentityHashMap<Object, Set<ScheduledTask>>(16);
If you just want to cancel the a repeated scheduled task, you can just do like following (here is a runnable demo in my repo):如果您只想取消重复的计划任务,您可以执行以下操作(这是我的 repo 中的可运行演示):
@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Autowired
private TestSchedule testSchedule;
public void later() {
postProcessor.postProcessBeforeDestruction(test, "testSchedule");
}
It will find this beans's ScheduledTask
and cancel it one by one.它会找到这个bean的
ScheduledTask
并一一取消。 What should be noticed is it will also stopping the current running method (as postProcessBeforeDestruction
source shows).应该注意的是,它还会停止当前正在运行的方法(如
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
}
}
Minimalist answer:极简回答:
@mahesh's option 1, expanded here in minimal form for convenience, will irreversibly cancel all scheduled tasks on this bean: @mahesh 的选项 1,为方便起见以最小形式在此处扩展,将不可逆转地取消此 bean 上的所有计划任务:
@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Scheduled(fixedRate = 2000)
public void doSomething() {}
public void stopThis() {
postProcessBeforeDestruction(this, "")
}
Alternatively, this will irreversibly cancel all tasks on all beans:或者,这将不可逆转地取消所有bean 上的所有任务:
@Autowired
private ThreadPoolTaskScheduler scheduler;
@Scheduled(fixedRate = 2000)
public void doSomething() {}
public void stopAll() {
scheduler.shutdown();
}
Thanks, all the previous responders, for solving this one for me.谢谢,所有以前的响应者,为我解决了这个问题。
Define a custom annotation like below.定义一个自定义注释,如下所示。
@Documented
@Retention (RUNTIME)
@Target(ElementType.TYPE)
public @interface ScheduledSwitch {
// do nothing
}
Define a class implements org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.定义一个类实现 org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor。
public class ScheduledAnnotationBeanPostProcessorCustom
extends ScheduledAnnotationBeanPostProcessor {
@Value(value = "${prevent.scheduled.tasks:false}")
private boolean preventScheduledTasks;
private Map<Object, String> beans = new HashMap<>();
private final ReentrantLock lock = new ReentrantLock(true);
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
ScheduledSwitch switch = AopProxyUtils.ultimateTargetClass(bean)
.getAnnotation(ScheduledSwitch.class);
if (null != switch) {
beans.put(bean, beanName);
if (preventScheduledTasks) {
return bean;
}
}
return super.postProcessAfterInitialization(bean, beanName);
}
public void stop() {
lock.lock();
try {
for (Map.Entry<Object, String> entry : beans.entrySet()) {
postProcessBeforeDestruction(entry.getKey(), entry.getValue());
}
} finally {
lock.unlock();
}
}
public void start() {
lock.lock();
try {
for (Map.Entry<Object, String> entry : beans.entrySet()) {
if (!requiresDestruction(entry.getKey())) {
super.postProcessAfterInitialization(
entry.getKey(), entry.getValue());
}
}
} finally {
lock.unlock();
}
}
}
Replace ScheduledAnnotationBeanPostProcessor bean by the custom bean in configuration.用配置中的自定义 bean 替换 ScheduledAnnotationBeanPostProcessor bean。
@Configuration
public class ScheduledConfig {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor() {
return new ScheduledAnnotationBeanPostProcessorCustom();
}
}
Add @ScheduledSwitch annotation to the beans that you want to prevent or stop @Scheduled tasks.向要阻止或停止 @Scheduled 任务的 bean 添加 @ScheduledSwitch 注释。
Using @conditional<\/code> will help you check a value from condition method, if it's true?
使用
@conditional<\/code>将帮助您检查条件方法中的值,如果它是真的?
run the scheduler.
运行调度程序。 else don't run.
否则不要运行。
First: create your condition class that implements the Condition interface and its
matches<\/code> method
首先:创建实现 Condition 接口及其
matches<\/code>方法的条件类
public class MyCondition implements Condition{
public boolean matches(ConditionContext context, AnnotatedTypeMetaData metadata) {
// here implement your condition using if-else or checking another object
// or call another method that can return boolean value
//return boolean value : true or false
return true;
}
}
import com.google.common.collect.Maps;
import org.redisson.liveobject.misc.ClassUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.emptySet;
import com.google.common.collect.Maps;
import org.redisson.liveobject.misc.ClassUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.emptySet;
/**
* @author uhfun
*/
@Component
public class ConfigurableScheduler {
private final InnerScheduledAnnotationBeanPostProcessor postProcessor;
public ConfigurableScheduler(InnerScheduledAnnotationBeanPostProcessor postProcessor) {
this.postProcessor = postProcessor;
}
public void registerScheduleTask(String cron, Method method, Object target) {
Map<String, Object> attributes = Maps.newHashMap();
attributes.put("cron", cron);
Scheduled scheduled = AnnotationUtils.synthesizeAnnotation(attributes, Scheduled.class, null);
postProcessor.registerScheduleTask(scheduled, method, target);
}
public void unregister(String cron, Object target) {
postProcessor.unregister(target, cron);
}
@Component
public static class InnerScheduledAnnotationBeanPostProcessor extends ScheduledAnnotationBeanPostProcessor {
private final Map<Object, Set<ScheduledTask>> scheduledTasksMap;
public InnerScheduledAnnotationBeanPostProcessor() {
scheduledTasksMap = ClassUtils.getField(this, "scheduledTasks");
}
public void registerScheduleTask(Scheduled scheduled, Method method, Object bean) {
super.processScheduled(scheduled, method, bean);
}
public void unregister(Object bean, String cron) {
synchronized (scheduledTasksMap) {
Set<ScheduledTask> tasks = getScheduledTasks();
for (ScheduledTask task : tasks) {
if (task.getTask() instanceof CronTask
&& ((CronTask) task.getTask()).getExpression().equals(cron)) {
task.cancel();
scheduledTasksMap.getOrDefault(bean, emptySet()).remove(task);
}
}
}
}
}
}
How about using System.exit(1)
?使用
System.exit(1)
怎么样? It is simple and works.它很简单并且有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.