@RefreshScope stops @Scheduled task

前端 未结 4 710
心在旅途
心在旅途 2020-12-11 22:48

I have a monitoring app wherein I am running a fixedRate task. This is pulling in a config parameter configured with Consul. I want to pull in updated configuration, so I ad

相关标签:
4条回答
  • 2020-12-11 23:09

    My solution consists of listening to EnvironmentChangeEvent

    @Configuration
    public class SchedulingSpringConfig implements ApplicationListener<EnvironmentChangeEvent>, SchedulingConfigurer {
    
      private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingSpringConfig.class);
    
      private final DemoProperties demoProperties;
    
      public SchedulingSpringConfig(DemoProperties demoProperties) {
        this.demoProperties = demoProperties;
      }
    
      @Override
      public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        LOGGER.info("Configuring scheduled task with cron expression: {}", demoProperties.getCronExpression());
        taskRegistrar.addTriggerTask(triggerTask());
        taskRegistrar.setTaskScheduler(taskScheduler());
      }
    
      @Bean
      public TriggerTask triggerTask() {
        return new TriggerTask(this::work, cronTrigger());
      }
    
      private void work() {
        LOGGER.info("Doing work!");
      }
    
      @Bean
      @RefreshScope
      public CronTrigger cronTrigger() {
        return new CronTrigger(demoProperties.getCronExpression());
      }
    
      @Bean
      public ThreadPoolTaskScheduler taskScheduler() {
        return new ThreadPoolTaskScheduler();
      }
    
      @Override
      public void onApplicationEvent(EnvironmentChangeEvent event) {
        if (event.getKeys().contains("demo.config.cronExpression")) {
          ScheduledTasksRefresher scheduledTasksRefresher = new ScheduledTasksRefresher(triggerTask());
          scheduledTasksRefresher.afterPropertiesSet();
        }
      }
    }
    

    Then I use the ContextLifecycleScheduledTaskRegistrar to recreate the task.

    public class ScheduledTasksRefresher extends ContextLifecycleScheduledTaskRegistrar {
    
      private final TriggerTask triggerTask;
    
      ScheduledTasksRefresher(TriggerTask triggerTask) {
        this.triggerTask = triggerTask;
      }
    
      @Override
      public void afterPropertiesSet() {
        super.destroy();
        super.addTriggerTask(triggerTask);
        super.afterSingletonsInstantiated();
      }
    }
    

    Properties definition:

    @ConfigurationProperties(prefix = "demo.config", ignoreUnknownFields = false)
    public class DemoProperties {
    
      private String cronExpression;
    
      public String getCronExpression() {
        return cronExpression;
      }
    
      public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
      }
    }
    

    Main definition:

    @SpringBootApplication
    @EnableConfigurationProperties(DemoProperties.class)
    @EnableScheduling
    public class DemoApplication {
    
      public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
      }
    }
    
    0 讨论(0)
  • 2020-12-11 23:10

    I have done workaround for this kind of scenario by implementing SchedulingConfigurer interface. Here I am dynamically updating "scheduler.interval" property from external property file and scheduler is working fine even after actuator refresh as I am not using @RefreshScope anymore. Hope this might help you in your case also.

    public class MySchedulerImpl implements SchedulingConfigurer {
    
        @Autowired
        private Environment env;
    
        @Bean(destroyMethod = "shutdown")
        public Executor taskExecutor() {
            return Executors.newScheduledThreadPool(10);
        }
    
        @Override
        public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
            taskRegistrar.setScheduler(this.taskExecutor());
            taskRegistrar.addTriggerTask(() -> {
                //put your code here that to be scheduled
            }, triggerContext -> {
                final Calendar nextExecutionTime = new GregorianCalendar();
                final Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
    
                if (lastActualExecutionTime == null) {
                    nextExecutionTime.setTime(new Date());
                } else {
                    nextExecutionTime.setTime(lastActualExecutionTime);
                    nextExecutionTime.add(Calendar.MILLISECOND, env.getProperty("scheduler.interval", Integer.class));
                }
                return nextExecutionTime.getTime();
            });
        }
    }
    
    0 讨论(0)
  • 2020-12-11 23:14

    Here's how we've solved this issue.

    /**
     * Listener of Spring's lifecycle to revive Scheduler beans, when spring's
     * scope is refreshed.
     * <p>
     * Spring is able to restart beans, when we change their properties. Such a
     * beans marked with RefreshScope annotation. To make it work, spring creates
     * <b>lazy</b> proxies and push them instead of real object. The issue with
     * scope refresh is that right after refresh in order for such a lazy proxy
     * to be actually instantiated again someone has to call for any method of it.
     * <p>
     * It creates a tricky case with Schedulers, because there is no bean, which
     * directly call anything on any Scheduler. Scheduler lifecycle is to start
     * few threads upon instantiation and schedule tasks. No other bean needs
     * anything from them.
     * <p>
     * To overcome this, we had to create artificial method on Schedulers and call
     * them, when there is a scope refresh event. This actually instantiates.
     */
    @RequiredArgsConstructor
    public class RefreshScopeListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
        private final List<RefreshScheduler> refreshSchedulers;
    
        @Override
        public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
            refreshSchedulers.forEach(RefreshScheduler::materializeAfterRefresh);
        }
    }
    

    So, we've defined an interface, which does nothing in particular, but allows us to call for a refreshed job.

    public interface RefreshScheduler {
        /**
         * Used after refresh context for scheduler bean initialization
         */
        default void materializeAfterRefresh() {
        }
    }
    

    And here is actual job, whose parameter from.properties can be refreshed.

    public class AJob implements RefreshScheduler {
        @Scheduled(cron = "${from.properties}")
        public void aTask() {
            // do something useful
        }
    }
    

    UPDATED: Of course AJob bean must be marked with @RefreshScope in @Configuration

    @Configuration
    @EnableScheduling
    public class SchedulingConfiguration {
        @Bean
        @RefreshScope
        public AJob aJob() {
            return new AJob();
        }
    }
    
    0 讨论(0)
  • 2020-12-11 23:19

    I'm successfully get & override the values from consul config server using RefreshScopeRefreshedEvent

    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
    import org.springframework.context.ApplicationListener;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component
    @RefreshScope
    public class AlertSchedulerCron implements ApplicationListener<RefreshScopeRefreshedEvent> {
    
        private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Value("${pollingtime}")
        private String pollingtime;
        
        /*
         * @Value("${interval}") private String interval;
         */
        @Scheduled(cron = "${pollingtime}")
        //@Scheduled(fixedRateString = "${interval}" )
        public void task() {
    
            System.out.println(pollingtime);
            System.out.println("Scheduler (cron expression) task with duration : " + sdf.format(new Date()));
        }
    
        @Override
        public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
            // TODO Auto-generated method stub
            
        }
    
        
    }
    
    0 讨论(0)
提交回复
热议问题