«1. Обзор

Quartz — это фреймворк планирования заданий с открытым исходным кодом, полностью написанный на Java и предназначенный для использования в приложениях J2SE и J2EE. Он предлагает большую гибкость без ущерба для простоты.

Вы можете создавать сложные расписания для выполнения любого задания. Примеры, например. задачи, которые выполняются ежедневно, каждую вторую пятницу в 19:30. или только в последний день каждого месяца.

В этой статье мы рассмотрим элементы для создания задания с Quartz API. Для ознакомления с Spring мы рекомендуем Scheduling in Spring with Quartz.

2. Зависимости Maven

Нам нужно добавить следующую зависимость в pom.xml:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>

Последнюю версию можно найти в центральном репозитории Maven.

3. Quartz API

Сердцем фреймворка является Планировщик. Он отвечает за управление средой выполнения нашего приложения.

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

Вот как фреймворк может запускать множество заданий одновременно. Он также опирается на слабосвязанный набор компонентов управления ThreadPool для управления средой потоков.

Ключевые интерфейсы API:

    Scheduler — основной API для взаимодействия с планировщиком фреймворка Job — интерфейс, реализуемый компонентами, которые мы хотим выполнить JobDetail — используется для определения экземпляров Jobs Trigger — компонента, определяющего расписание, по которому будет выполняться данное задание JobBuilder — используется для создания экземпляров JobDetail, определяющих экземпляры Jobs TriggerBuilder — используется для создания экземпляров Trigger ~~ ~ Давайте посмотрим на каждый из этих компонентов.

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

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

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

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();

Однако Планировщик не будет действовать ни на какие триггеры, пока он не будет запущен с помощью метода start():

5. Задания

scheduler.start();

Задание — это класс, реализующий интерфейс Задания. У него есть только один простой метод:

Когда срабатывает триггер задания, метод execute() вызывается одним из рабочих потоков планировщика.

public class SimpleJob implements Job {
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        System.out.println("This is a quartz job!");
    }
}

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

Объект JobDetail создается клиентом Quartz во время добавления задания в планировщик. По сути, это определение экземпляра задания:

Этот объект также может содержать различные настройки свойств для задания, а также JobDataMap, который можно использовать для хранения информации о состоянии для данного экземпляра нашего класса задания. .

JobDetail job = JobBuilder.newJob(SimpleJob.class)
  .withIdentity("myJob", "group1")
  .build();

5.1. JobDataMap

JobDataMap используется для хранения любого количества объектов данных, которые мы хотим сделать доступными для экземпляра задания при его выполнении. JobDataMap является реализацией интерфейса Java Map и имеет несколько дополнительных удобных методов для хранения и извлечения данных примитивных типов.

Вот пример помещения данных в JobDataMap при построении JobDetail перед добавлением задания в планировщик:

А вот пример доступа к этим данным во время выполнения задания:

JobDetail job = newJob(SimpleJob.class)
  .withIdentity("myJob", "group1")
  .usingJobData("jobSays", "Hello World!")
  .usingJobData("myFloatValue", 3.141f)
  .build();

В приведенном выше примере будет напечатано «Job говорит Hello World!», а val равно 3,141».

public class SimpleJob implements Job { 
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();

        String jobSays = dataMap.getString("jobSays");
        float myFloatValue = dataMap.getFloat("myFloatValue");

        System.out.println("Job says: " + jobSays + ", and val is: " + myFloatValue);
    } 
}

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

«Если мы сделаем это, реализация Quartz по умолчанию JobFactory автоматически вызовет эти установщики при создании экземпляра задания, что избавит от необходимости явно получать значения из карты в нашем методе выполнения.

6. Триггеры

Объекты-триггеры используются для запуска выполнения заданий.

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

Триггер также может иметь связанный с ним JobDataMap. Это полезно для передачи в задание параметров, относящихся к выполнению триггера.

Trigger trigger = TriggerBuilder.newTrigger()
  .withIdentity("myTrigger", "group1")
  .startNow()
  .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    .withIntervalInSeconds(40)
    .repeatForever())
  .build();

Существуют различные типы триггеров для различных потребностей планирования. Каждый из них имеет разные свойства TriggerKey для отслеживания их личности. Однако некоторые другие свойства являются общими для всех типов триггеров:

Свойство jobKey указывает идентификатор задания, которое должно выполняться при срабатывании триггера. Свойство startTime указывает, когда расписание триггера впервые вступает в силу. Значение представляет собой объект java.util.Date, определяющий момент времени для данной календарной даты. Для некоторых типов триггеров триггер срабатывает в заданное время запуска. Для других он просто отмечает время начала расписания. Свойство endTime указывает, когда расписание триггера должно быть отменено.

    Quartz поставляется с несколькими различными типами триггеров, но наиболее часто используемыми являются SimpleTrigger и CronTrigger.

6.1. Приоритет

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

Например, если десять триггеров настроены на одновременное срабатывание и доступно только четыре рабочих потока, первые четыре триггера с наивысшим приоритетом будут выполняться первыми. Когда мы не устанавливаем приоритет для триггера, он использует приоритет по умолчанию, равный пяти. В качестве приоритета допускается любое целочисленное значение, положительное или отрицательное.

В приведенном ниже примере у нас есть два триггера с разным приоритетом. Если ресурсов недостаточно для одновременного срабатывания всех триггеров, первым сработает триггер A:

6.2. Инструкции по осечкам

Trigger triggerA = TriggerBuilder.newTrigger()
  .withIdentity("triggerA", "group1")
  .startNow()
  .withPriority(15)
  .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    .withIntervalInSeconds(40)
    .repeatForever())
  .build();
            
Trigger triggerB = TriggerBuilder.newTrigger()
  .withIdentity("triggerB", "group1")
  .startNow()
  .withPriority(10)
  .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    .withIntervalInSeconds(20)
    .repeatForever())
  .build();

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

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

Давайте взглянем на примеры ниже:

Мы запланировали запуск триггера на 10 секунд назад (поэтому к моменту его создания он запаздывает на 10 секунд) для имитации осечки, т.е. потому что планировщик был недоступен или не имел достаточного количества доступных рабочих потоков. Конечно, в реальном сценарии мы бы никогда не запланировали такие триггеры.

Trigger misFiredTriggerA = TriggerBuilder.newTrigger()
  .startAt(DateUtils.addSeconds(new Date(), -10))
  .build();
            
Trigger misFiredTriggerB = TriggerBuilder.newTrigger()
  .startAt(DateUtils.addSeconds(new Date(), -10))
  .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    .withMisfireHandlingInstructionFireNow())
  .build();

В первом триггере (misFiredTriggerA) инструкции по обработке осечек не заданы. Следовательно, в этом случае используется вызываемая интеллектуальная политика, которая называется: withMisfireHandlingInstructionFireNow(). Это означает, что задание выполняется сразу после того, как планировщик обнаружит осечку.

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

6.3. SimpleTrigger

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

Примером может быть запуск выполнения задания ровно в 00:20:00 13 января 2018 года. Точно так же мы можем начать в это время, а затем еще пять раз каждые десять секунд.

«В приведенном ниже коде дата myStartTime была определена ранее и используется для создания триггера для одной конкретной метки времени:

Далее, давайте создадим триггер для определенного момента времени, затем повторяя каждые десять секунд десять раз. :

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
  .withIdentity("trigger1", "group1")
  .startAt(myStartTime)
  .forJob("job1", "group1")
  .build();

6.4. CronTrigger

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
  .withIdentity("trigger2", "group1")
  .startAt(myStartTime)
  .withSchedule(simpleSchedule()
    .withIntervalInSeconds(10)
    .withRepeatCount(10))
  .forJob("job1") 
  .build();

CronTrigger используется, когда нам нужны расписания, основанные на операторах, подобных календарю. Например, мы можем указать расписание запуска, например, каждую пятницу в полдень или каждый будний день в 9:30.

Выражения Cron используются для настройки экземпляров CronTrigger. Эти выражения состоят из строк, состоящих из семи подвыражений. Мы можем прочитать больше о Cron-Expressions здесь.

В приведенном ниже примере мы создаем триггер, который срабатывает каждую вторую минуту между 8:00 и 17:00 каждый день:

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

CronTrigger trigger = TriggerBuilder.newTrigger()
  .withIdentity("trigger3", "group1")
  .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?"))
  .forJob("myJob", "group1")
  .build();

В этой статье мы показали, как создать Планировщик для запуска задания. Мы также видели некоторые из наиболее распространенных вариантов запуска: SimpleTrigger и CronTrigger.

Quartz можно использовать для создания простых или сложных расписаний для выполнения десятков, сотен или даже большего количества заданий. Более подробную информацию о фреймворке можно найти на основном сайте.

Исходный код примеров можно найти на GitHub.

«