Мне пришлось спросить себя, имеет ли смысл пытаться настроить задание и добавить проверки, если оно уже было выполнено, как это было предложено в ответе Марко Лахмы (поскольку планирование запуска задания один раз приводит к тому, что оно запускается один раз, каждый раз мы запускаем приложение). Я нашел примеры приложений CommandLineRunner, которые у меня не совсем работали, в основном потому, что у нас уже был ApplicationRunner, который использовался для других заданий, использующих Quartz scheduling / cron. Я не был доволен тем, что Quartz инициализировал эту работу с помощью SimpleTrigger, поэтому мне пришлось искать что-то еще.
Используя некоторые идеи из следующих статей:
Мне удалось собрать воедино работающую реализацию, которая позволяет мне делать следующее:
- запускать существующие задания через Quartz, по таймеру
- запускать новое задание, один раз программно (одноразовое задание Quartz с использованием SimpleTrigger не удовлетворяло моим требованиям, так как оно будет запускаться один раз при каждой загрузке приложения)
Я придумал следующий класс CommandLineRunner:
public class BatchCommandLineRunner implements CommandLineRunner {
@Autowired
private Scheduler scheduler;
private static final Logger LOGGER = LoggerFactory.getLogger(BatchCommandLineRunner.class);
public void run(final String... args) throws SchedulerException {
LOGGER.info("BatchCommandLineRunner: running with args -> " + Arrays.toString(args));
for (final String jobName : args) {
final JobKey jobKey = findJobKey(jobName);
if (jobKey != null) {
LOGGER.info("Triggering job for: " + jobName);
scheduler.triggerJob(jobKey);
} else {
LOGGER.info("No job found for jobName: " + jobName);
}
}
}
private JobKey findJobKey(final String jobNameToFind) throws SchedulerException {
for (final JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals("DEFAULT"))) {
final String jobName = jobKey.getName();
if (jobName.equals(jobNameToFind)) {
return jobKey;
}
}
return null;
}
}
В одном из моих классов конфигурации я добавил bean-компонент CommandLineRunner, который вызывает созданный мной пользовательский CommandLineRunner:
@Configuration
public class BatchConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(BatchConfiguration.class);
@Bean
public BatchCommandLineRunner batchCommandLineRunner() {
return new BatchCommandLineRunner();
}
@Bean
public CommandLineRunner runCommandLineArgs(final ApplicationArguments applicationArguments) throws Exception {
final List<String> jobNames = applicationArguments.getOptionValues("jobName");
LOGGER.info("runCommandLineArgs: running the following jobs -> " + ArrayUtils.toString(jobNames));
batchCommandLineRunner().run(jobNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY));
return null;
}
}
Позже я могу запускать эти задания через интерфейс командной строки, не затрагивая мои текущие запланированные задания Quartz, и пока никто не запускает команду через интерфейс командной строки несколько раз, она никогда больше не будет выполняться. Я должен сделать несколько типов, так как я принимаю ApplicationArguments, а затем преобразовываю их в String [].
Наконец, я могу назвать это так:
java -jar <your_application>.jar --jobName=<QuartzRegisteredJobDetailFactoryBean>
В результате задание инициализируется только при его вызове и исключается из моих триггеров CronTriggerFactoryBean, которые я использовал для других своих заданий.
Здесь делается несколько предположений, поэтому я попытаюсь обобщить:
- задание должно быть зарегистрировано как JobDetailFactoryBean (например:
scheduler.setJobDetails(...)
)
- все по сути то же самое, что и работа с CronTriggerFactoryBean, за исключением отсутствующего
scheduler.setTriggers(...)
вызова
- Spring знает, как выполнять классы CommandLineRunner после загрузки приложения
- Я жестко закодировал параметр, передаваемый в приложение, в "jobName"
- Я принял название группы "DEFAULT" для всех заданий; если вы хотите использовать разные группы, это нужно будет отрегулировать при получении JobKey, который используется для фактического запуска задания
- нет ничего, что мешало бы запускать это задание несколько раз через CLI, но оно запускалось при каждой загрузке приложения с использованием подхода SimpleTrigger, так что это лучше для меня; если это неприемлемо, возможно, использование StepListener, ExitStatus и т. д. может помешать его выполнению дважды