简体   繁体   English

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

[英]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任务的功能?

Option 1: Using a post processor选项 1:使用后处理器

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)


Option 2: Maintaining a map of target beans to its Future选项 2:维护目标 bean 到它的 Future 的映射

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这个问题有点含糊

  1. When you say "stop this task", did you mean to stop in such a way that it's later recoverable (if yes, programmatically, using a condition which arises with in the same app? or external condition?)当您说“停止此任务”时,您的意思是要以以后可恢复的方式停止(如果是,以编程方式,使用在同一应用程序中出现的条件?或外部条件?)
  2. Are you running any other tasks in the same context?您是否在同一上下文中运行任何其他任务? (Possibility of shutting down the entire app rather than a task) -- You can make use of actuator.shutdown endpoint in this scenario (关闭整个应用程序而不是任务的可能性)--您可以在这种情况下使用actuator.shutdown 端点

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.简单、清晰且线程安全。

  1. In your configuration class add annotation:在您的配置类中添加注释:

    @EnableScheduling

  2. This and next step in your class where you need start/stop scheduled task inject:这一步和你需要开始/停止计划任务注入的课程的下一步:

    @Autowired TaskScheduler taskScheduler;

  3. Set fields:设置字段:

     private ScheduledFuture yourTaskState; private long fixedRate = 1000L;
  4. Create inner class that execute scheduled tasks eg.:创建执行计划任务的内部类,例如:

     class ScheduledTaskExecutor implements Runnable{ @Override public void run() { // task to be executed } }
  5. Add start() method:添加 start() 方法:

     public void start(){ yourTaskState = taskScheduler.scheduleAtFixedRate(new ScheduledTaskExecutor(), fixedRate); }
  6. 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();

Scheduled预定

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);

Cancel取消

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");
}

Notice通知

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM