У меня есть приложение для мониторинга, в котором я выполняю задачу fixedRate. Это подтягивает параметр конфигурации, настроенный с помощью Consul. Я хочу использовать обновленную конфигурацию, поэтому добавил @RefreshScope. Но как только я обновляю значение конфигурации в Consul, задача fixedRate перестает выполняться.
@Service
@RefreshScope
public class MonitorService {
@Autowired
private AppConfig appConfig;
@PostConstruct
public void postConstRun() {
System.out.println(appConfig.getMonitorConfig());
}
@Scheduled(fixedRate = 1000)
public void scheduledMonitorScan() {
System.out.println("MonitorConfig:" + appConfig.getMonitorConfig());
}
}
Класс AppConfig имеет только один параметр String:
@Configuration
@Getter
@Setter
public class AppConfig {
@Value("${monitor-config:default value}")
private String monitorConfig;
}
Как только я обновляю значение в consul, запланированная задача просто перестает выполняться (отображается в методе sheduledMonitorScan) и перестает отображаться.
Что разделять? Вы имеете в виду переместить RefreshScope из класса MonitorService?
Вам просто нужен @RefreshScope в AppConfig, и это обновит внедренный bean-компонент в вашу службу.
Я пробовал добавить @ RefreshScope в AppConfig. Читая Документация Sprind, я обнаружил, что общедоступный метод обновления в аннотации предоставляется в конечной точке / refresh. Поэтому я добавил зависимость от пружинного привода. Теперь моя конфигурация обновляется, но @ Scheduled перестает работать, когда я добавляю пружинный привод.
Я сделал обходной путь для такого сценария, реализовав интерфейс SchedulingConfigurer. Здесь я динамически обновляю свойство "scheduler.interval" из внешнего файла свойств, и планировщик работает нормально даже после обновления привода, поскольку я больше не использую @RefreshScope. Надеюсь, это поможет и вам в вашем случае.
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();
});
}
}
Вот как мы решили эту проблему.
/**
* 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);
}
}
Итак, мы определили интерфейс, который ничего особенного не делает, но позволяет нам вызывать обновленное задание.
public interface RefreshScheduler {
/**
* Used after refresh context for scheduler bean initialization
*/
default void materializeAfterRefresh() {
}
}
А вот и актуальная работа, параметр from.properties которой можно обновить.
public class AJob implements RefreshScheduler {
@Scheduled(cron = "${from.properties}")
public void aTask() {
// do something useful
}
}
ОБНОВЛЕНО: Конечно, bean-компонент AJob должен быть помечен @RefreshScope в @Configuration
@Configuration
@EnableScheduling
public class SchedulingConfiguration {
@Bean
@RefreshScope
public AJob aJob() {
return new AJob();
}
}
Чтобы код заработал, мне пришлось добавить аннотацию @Component к RefreshScopeListener. Тогда код работает. Спасибо
Я чувствую, что весеннее облако должно делать это автоматически само. Но пока я думаю, это решение
Мое решение состоит в прослушивании 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();
}
}
}
Затем я использую ContextLifecycleScheduledTaskRegistrar, чтобы воссоздать задачу.
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();
}
}
Определение свойств:
@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;
}
}
Основное определение:
@SpringBootApplication
@EnableConfigurationProperties(DemoProperties.class)
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Спасибо. На мой взгляд, destroy () не уничтожает ранее запланированные задачи. Любая идея?
После некоторой отладки, мое наблюдение. У объекта-регистратора нет запланированных заданий (сюрприз!). Я также попытался сохранить регистратор в переменной экземпляра из configuretasks и запустить на нем уничтожение. Но такое же поведение!
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
}
}
Это должно быть приемлемым решением. Безусловно, самый простой в использовании!
После этого решения мой планировщик запускается два раза. Я вижу, что на самом деле похоже, что есть 2 экземпляра планировщика, один работает со старым расписанием, а другой - с новым расписанием.
Вы пробовали их разделить?