Сбрасывать состояние перед каждым выполнением запланированного 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
Другие вопросы по тегам