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

时间:2017-06-20 04:59:39

标签: java spring scheduled-tasks

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

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

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

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

Spring提供了什么来阻止@Scheduled任务?

9 个答案:

答案 0 :(得分:15)

选项1:使用后处理器

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


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

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

当必须停止bean的计划时,您可以查找地图以获取相应的Future并明确取消它。

答案 1 :(得分:6)

这个问题有点含糊不清

  1. 当你说&#34;停止这个任务&#34;时,你的意思是停下来以后它可以恢复(如果是的话,使用在同一个应用程序中出现的条件,编程) ?或外部条件?)
  2. 您是否在同一上下文中运行任何其他任务? (关闭整个应用程序而不是任务的可能性) - 您可以在此方案中使用actuator.shutdown端点
  3. 我最好的猜测是,您希望使用可能以可恢复的方式在同一个应用中出现的条件来关闭任务。我会尝试根据这个假设回答。

    这是我能想到的simplest possible解决方案,但是我会做出一些改进,比如早期返回,而不是嵌套,如果 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...
       }
    

    上述代码的属性

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

答案 2 :(得分:5)

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

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

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

答案 3 :(得分:2)

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

@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 !";
    }
}

}

答案 4 :(得分:2)

使用ScheduledAnnotationBeanPostProcessor.postProcessBeforeDestruction(bean, beanName)的@Mahesh选项1的工作示例实现。

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

答案 5 :(得分:1)

我尚未找到的另一种方法。简单,清晰且线程安全。

  1. 在您的配置类中添加注释:

    @EnableScheduling

  2. 您需要启动/停止计划任务注入的课程中的此步骤和下一步:

    @Autowired TaskScheduler taskScheduler;

  3. 设置字段:

     private ScheduledFuture yourTaskState;
     private long fixedRate = 1000L;
    
  4. 创建执行计划任务的内部类,例如:

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

     public void start(){
         yourTaskState = taskScheduler.scheduleAtFixedRate(new ScheduledTaskExecutor(), fixedRate);
     }
    
  6. 添加stop()方法:

     public void stop(){
         yourTaskState.cancel(false);
     }
    

TaskScheduler提供了其他常见的计划方式,例如cron或delay。

ScheduledFuture还提供isCancelled();

答案 6 :(得分:1)

极简答案:
@mahesh 的选项 1,为了方便起见以最小形式在此处扩展,将不可逆转地取消此 bean 上的所有计划任务:

@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;

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

public void stopThis() {
    postProcessBeforeDestruction(this, "")
}

或者,这将不可逆转地取消所有 bean 上的所有任务:

@Autowired
private ThreadPoolTaskScheduler scheduler;

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

public void stopAll() {
    scheduler.shutdown();
}

感谢之前所有的回复者为我解决了这个问题。

答案 7 :(得分:0)

预定

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

private final Map<Object, Set<ScheduledTask>> scheduledTasks =
        new IdentityHashMap<Object, Set<ScheduledTask>>(16);

取消

如果您只想取消重复的计划任务,您可以执行以下操作(此处为我的仓库中的runnable demo):

@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
        }
    }

答案 8 :(得分:0)

定义如下的自定义注释。

@Documented
@Retention (RUNTIME)
@Target(ElementType.TYPE)
public @interface ScheduledSwitch {
    // do nothing
}

定义一个类,以实现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();
        }
    }

}

在配置中用自定义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();
    }

}

将@ScheduledSwitch批注添加到要阻止或停止@Scheduled任务的bean。