«1. Обзор

В этом уроке мы создадим простой Планировщик на Spring с помощью Quartz.

Мы начнем с простой цели — легко настроить новое запланированное задание.

1.1. Ключевые компоненты Quartz API

Quartz имеет модульную архитектуру. Он состоит из нескольких основных компонентов, которые можно комбинировать по мере необходимости. В этом руководстве мы сосредоточимся на тех, которые являются общими для всех заданий: Job, JobDetail, Trigger и Scheduler.

Хотя мы будем использовать Spring для управления приложением, каждый отдельный компонент можно настроить двумя способами: способом Quartz или способом Spring (используя его удобные классы).

Мы рассмотрим оба варианта для полноты картины, но любой из них может быть принят. Давайте начнем строить, по одному компоненту за раз.

2. Job и JobDetail

2.1. Job

API предоставляет интерфейс Job, имеющий только один метод – execute. Он должен быть реализован классом, который содержит фактическую работу, которую нужно выполнить, то есть задачу. Когда срабатывает триггер задания, планировщик вызывает метод execute, передавая ему объект JobExecutionContext.

JobExecutionContext предоставляет экземпляру задания информацию о его среде выполнения, включая дескриптор планировщика, дескриптор триггера и объект задания JobDetail.

В этом кратком примере задание делегирует задачу классу обслуживания:

@Component
public class SampleJob implements Job {

    @Autowired
    private SampleJobService jobService;

    public void execute(JobExecutionContext context) throws JobExecutionException {
        jobService.executeSampleJob();
    }
}

2.2. JobDetail

Хотя задание является рабочей лошадкой, Quartz не хранит реальный экземпляр класса задания. Вместо этого мы можем определить экземпляр Job с помощью класса JobDetail. Класс задания должен быть предоставлен JobDetail, чтобы ему был известен тип выполняемого задания.

2.3. Quartz JobBuilder

Quartz JobBuilder предоставляет API в стиле конструктора для создания сущностей JobDetail.

@Bean
public JobDetail jobDetail() {
    return JobBuilder.newJob().ofType(SampleJob.class)
      .storeDurably()
      .withIdentity("Qrtz_Job_Detail")  
      .withDescription("Invoke Sample Job service...")
      .build();
}

2.4. Spring JobDetailFactoryBean

Spring JobDetailFactoryBean обеспечивает использование в стиле bean-компонента для настройки экземпляров JobDetail. Он использует имя bean-компонента Spring в качестве имени задания, если не указано иное:

@Bean
public JobDetailFactoryBean jobDetail() {
    JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
    jobDetailFactory.setJobClass(SampleJob.class);
    jobDetailFactory.setDescription("Invoke Sample Job service...");
    jobDetailFactory.setDurability(true);
    return jobDetailFactory;
}

Новый экземпляр JobDetail создается для каждого выполнения задания. Объект JobDetail передает подробные свойства задания. После завершения выполнения ссылки на экземпляр удаляются.

3. Триггер

Триггер — это механизм планирования задания, т. е. экземпляр триггера «запускает» выполнение задания. Существует четкое разделение обязанностей между заданием (понятие задачи) и триггером (механизм планирования).

В дополнение к Job триггеру также нужен тип, который можно выбрать на основе требований планирования.

Допустим, мы хотим запланировать выполнение нашей задачи один раз в час, на неопределенный срок — для этого мы можем использовать TriggerBuilder Quartz или SimpleTriggerFactoryBean Spring.

3.1. Quartz TriggerBuilder

TriggerBuilder — это API в стиле конструктора для создания объекта Trigger:

@Bean
public Trigger trigger(JobDetail job) {
    return TriggerBuilder.newTrigger().forJob(job)
      .withIdentity("Qrtz_Trigger")
      .withDescription("Sample trigger")
      .withSchedule(simpleSchedule().repeatForever().withIntervalInHours(1))
      .build();
}

3.2. Spring SimpleTriggerFactoryBean

SimpleTriggerFactoryBean обеспечивает использование в стиле bean-компонента для настройки SimpleTrigger. Он использует имя bean-компонента Spring в качестве имени триггера и по умолчанию использует бесконечное повторение, если не указано иное:

@Bean
public SimpleTriggerFactoryBean trigger(JobDetail job) {
    SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
    trigger.setJobDetail(job);
    trigger.setRepeatInterval(3600000);
    trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
    return trigger;
}

4. Настройка хранилища заданий

JobStore предоставляет механизм хранения заданий и триггеров и отвечает за сохранение всех данных, относящихся к планировщику заданий. API поддерживает как in-memory, так и постоянные хранилища.

4.1. In-Memory JobStore

В качестве примера мы будем использовать in-memory RAMJobStore, который обеспечивает молниеносную производительность и простую настройку с помощьюquart.properties:

org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

Очевидным недостатком RAMJobStore является его непостоянство. в природе. Вся информация о расписании теряется между отключениями. Если определения заданий и расписания должны сохраняться между завершениями работы, вместо этого следует использовать постоянное хранилище JDBCJobStore. Чтобы включить хранилище заданий в памяти в Spring, мы устанавливаем это свойство в нашем application.properties:

spring.quartz.job-store-type=memory

4.2. Магазин вакансий JDBC

«Существует два типа JDBCJobStore: JobStoreTX и JobStoreCMT. Оба они выполняют одну и ту же работу по хранению информации о расписании в базе данных.

Разница между ними заключается в том, как они управляют транзакциями, фиксирующими данные. Для типа JobStoreCMT требуется транзакция приложения для хранения данных, тогда как тип JobStoreTX запускает и управляет своими собственными транзакциями.

Для JDBCJobStore необходимо установить несколько свойств. Как минимум, мы должны указать тип JDBCJobStore, источник данных и класс драйвера базы данных. Для большинства баз данных существуют классы драйверов, но StdJDBCDelegate охватывает большинство случаев:

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=quartzDataSource

Настройка JDBC JobStore в Spring выполняется в несколько шагов. Во-первых, мы устанавливаем тип хранилища в нашем application.properties:

spring.quartz.job-store-type=jdbc

Далее нам нужно включить автоконфигурацию и предоставить Spring источник данных, необходимый планировщику Quartz. Аннотация @QuartzDataSource выполняет за нас тяжелую работу по настройке и инициализации базы данных Quartz:

@Configuration
@EnableAutoConfiguration
public class SpringQrtzScheduler {

    @Bean
    @QuartzDataSource
    public DataSource quartzDataSource() {
        return DataSourceBuilder.create().build();
    }
}

5. Планировщик

Интерфейс планировщика — это основной API для взаимодействия с планировщиком заданий.

Планировщик может быть создан с помощью SchedulerFactory. После создания задания и триггеры могут быть зарегистрированы в нем. Первоначально планировщик находится в режиме ожидания, и его метод запуска должен быть вызван для запуска потоков, запускающих выполнение заданий.

5.1. Quartz StdSchedulerFactory

Просто вызвав метод getScheduler в StdSchedulerFactory, мы можем создать экземпляр планировщика, инициализировать его (с настроенными JobStore и ThreadPool) и вернуть дескриптор его API:

@Bean
public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory) 
  throws SchedulerException {
    Scheduler scheduler = factory.getScheduler();
    scheduler.scheduleJob(job, trigger);
    scheduler.start();
    return scheduler;
}

5.2. Spring SchedulerFactoryBean

Spring SchedulerFactoryBean обеспечивает использование в стиле bean-компонента для настройки Scheduler, управляет его жизненным циклом в контексте приложения и предоставляет Scheduler как bean-компонент для внедрения зависимостей:

@Bean
public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) {
    SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
    schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));

    schedulerFactory.setJobFactory(springBeanJobFactory());
    schedulerFactory.setJobDetails(job);
    schedulerFactory.setTriggers(trigger);
    schedulerFactory.setDataSource(quartzDataSource);
    return schedulerFactory;
}

5.3. Настройка SpringBeanJobFactory

SpringBeanJobFactory обеспечивает поддержку внедрения контекста планировщика, карты данных задания и записей данных триггера в виде свойств в компонент задания при создании экземпляра.

Однако в нем отсутствует поддержка внедрения ссылок на bean-компоненты из контекста приложения. Благодаря автору этого сообщения в блоге, мы можем добавить поддержку автоматического связывания в SpringBeanJobFactory следующим образом:

@Bean
public SpringBeanJobFactory springBeanJobFactory() {
    AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
    jobFactory.setApplicationContext(applicationContext);
    return jobFactory;
}

6. Заключение

Вот и все. Мы только что создали наш первый базовый планировщик, используя Quartz API, а также удобные классы Spring.

Ключевым выводом из этого руководства является то, что мы смогли настроить задание с помощью всего нескольких строк кода и без использования какой-либо конфигурации на основе XML.

Полный исходный код примера доступен в этом проекте github. Это проект Maven, который можно импортировать и запускать как есть. По умолчанию используются удобные классы Spring, которые можно легко переключить на Quartz API с параметром времени выполнения (см. README.md в репозитории).