Сбрасывать состояние перед каждым выполнением запланированного Spring (@Scheduled)
У меня есть приложение Spring Boot Batch, которое нужно запускать ежедневно. Он читает ежедневный файл, выполняет некоторую обработку своих данных и записывает обработанные данные в базу данных. По ходу дела приложение содержит некоторое состояние, например, файл для чтения (хранится в FlatFileItemReader
а также JobParameters
), текущая дата и время запуска, некоторые данные файла для сравнения прочитанных элементов и т. д.
Одним из вариантов планирования является использование Spring @Scheduled
такие как:
@Scheduled(cron = "${schedule}")
public void runJob() throws Exception {
jobRunner.runJob(); //runs the batch job by calling jobLauncher.run(job, jobParameters);
}
Проблема здесь в том, что состояние поддерживается между запусками. Итак, мне нужно обновить файл для чтения, текущую дату и время запуска, очистить данные кэшированного файла и т. Д.
Другой вариант - запустить приложение через задание Unix cron. Это, очевидно, удовлетворит необходимость очистки состояния между запусками, но я предпочитаю связывать планирование заданий с приложением, а не с ОС (и предпочитаю, чтобы оно не зависело от ОС). Может ли состояние приложения быть сброшено между @Scheduled
работает?
2 ответа
Подход Томаса кажется разумным решением, поэтому я проголосовал за него. Чего не хватает, так это как это можно применить в случае весеннего пакетного задания. Поэтому я немного адаптировал его пример:
@Component
public class JobCreatorComponent {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Job createJob() {
// use the jobBuilderFactory to create your job as usual
return jobBuilderFactory.get() ...
}
}
ваш компонент с методом запуска @Component открытый класс ScheduledLauncher {
@Autowired
private ... jobRunner;
@Autwired
private JobCreatorComponent creator;
@Scheduled(cron = "${schedule}")
public void runJob() throws Exception {
// it would probably make sense to check the applicationContext and
// remove any existing job
creator.createJob(); // this should create a complete new instance of
// the Job
jobRunner.runJob(); //runs the batch job by calling jobLauncher.run(job, jobParameters);
}
Я не пробовал код, но такой подход я бы попробовал.
При построении задания важно убедиться, что все читатели, процессоры и писатели, используемые в этом задании, также являются полностью новыми экземплярами. Это означает, что если они не создаются как чистые Java-объекты (не как Spring Bean) или как Spring Bean с областью действия "step", вы должны убедиться, что всегда используется новый экземпляр.
Отредактировано: как обращаться с SingeltonBeans Иногда одноэлементные компоненты не могут быть предотвращены, в этих случаях должен быть способ их "перезагрузки".
Простой подход - определить интерфейс "ResetableBean" с методом сброса, который реализуется такими компонентами. Autowired может быть использован для сбора списка всех таких бинов.
@Component
public class ScheduledLauncher {
@Autowired
private List<ResetableBean> resetables;
...
@Scheduled(cron = "${schedule}")
public void runJob() throws Exception {
// reset all the singletons
resetables.forEach(bean -> bean.reset());
...
Вы всегда можете переместить код, который выполняет вашу задачу (и, что более важно, сохраняет ваше состояние) в bean-объект с прототипом. Затем вы можете получать новый экземпляр этого компонента из контекста приложения каждый раз, когда запускается запланированный метод.
пример
Я создал GitHub- репозиторий, который содержит рабочий пример того, о чем я говорю, но суть его в следующих двух классах:
ScheduledTask.java
Обратите внимание на @Scope
аннотаций. Он указывает, что этот компонент не должен быть одиночным. randomNumber
Поле представляет состояние, которое мы хотим сбросить при каждом вызове. "Сброс" в этом случае означает, что генерируется новое случайное число, просто чтобы показать, что оно действительно изменяется.
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class ScheduledTask {
private double randomNumber = Math.random();
void execute() {
System.out.printf(
"Executing task from %s. Random number is %f%n",
this,
randomNumber
);
}
}
TaskScheduler.java
По автопроводке в ApplicationContext
Вы можете использовать его внутри scheduledTask
метод для получения нового экземпляра ScheduledTask
,
@Component
public class TaskScheduler {
@Autowired
private ApplicationContext applicationContext;
@Scheduled(cron = "0/5 * * * * *")
public void scheduleTask() {
ScheduledTask task = applicationContext.getBean(ScheduledTask.class);
task.execute();
}
}
Выход
При запуске кода, вот пример того, как он выглядит:
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask@329c8d3d. Random number is 0.007027
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask@3c5b751e. Random number is 0.145520
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask@3864e64d. Random number is 0.268644