How to stop a scheduled task that was started using @Scheduled annotation?

后端 未结 8 1534
挽巷
挽巷 2020-11-27 03:52

I have created a simple scheduled task using Spring Framework\'s @Scheduled annotation.

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


        
相关标签:
8条回答
  • 2020-11-27 04:34

    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.

    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.

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

    0 讨论(0)
  • 2020-11-27 04:35

    Option 1: Using a post processor

    Supply ScheduledAnnotationBeanPostProcessor and explicitly invoke postProcessBeforeDestruction(Object bean, String beanName), for the bean whose scheduling should be stopped.


    Option 2: Maintaining a map of target beans to its 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.

    0 讨论(0)
  • 2020-11-27 04:35

    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

    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 ifs

    @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 * * ?
    
    0 讨论(0)
  • 2020-11-27 04:38

    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.

    0 讨论(0)
  • 2020-11-27 04:48

    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:

       public void start(){
           yourTaskState = taskScheduler.scheduleAtFixedRate(new ScheduledTaskExecutor(), fixedRate);
       }
      
    6. Add stop() method:

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

    TaskScheduler provide other common way for scheduling like: cron or delay.

    ScheduledFuture provide also isCancelled();

    0 讨论(0)
  • 2020-11-27 04:49

    A working example implementation of @Mahesh 's Option 1, using 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;
        }
    }
    
    
    0 讨论(0)
提交回复
热议问题