Quartz Scheduler для Java: полное руководство с примерами
1. Что это и зачем нужна
Quartz Scheduler — это мощная, промышленная библиотека для планирования задач (job scheduling) в приложениях на Java. Она предоставляет разработчикам гибкий и надёжный механизм для выполнения произвольного Java-кода в заданное время или по определённому расписанию. Quartz может быть интегрирован как в простые консольные утилиты, так и в сложные корпоративные системы, работающие на серверах приложений (Tomcat, JBoss, WebLogic) и в Spring-приложениях.
Зачем она нужна? В любом серьёзном приложении рано или поздно возникает необходимость выполнять фоновые операции: отправка email-рассылок, генерация отчётов, синхронизация данных с внешними системами, очистка временных файлов, проверка статусов заказов. Ручная реализация таких механизмов с помощью java.util.Timer или ScheduledExecutorService становится сложной и ненадёжной при росте требований: нужно управлять пропущенными задачами, кластеризацией, персистентностью расписаний, сложными cron-выражениями. Quartz решает все эти проблемы «из коробки».
Ключевые возможности Quartz: поддержка сложных триггеров (cron, простые интервалы, календари), персистентность задач и триггеров в базу данных (JDBC JobStore), кластеризация для обеспечения отказоустойчивости, слушатели для мониторинга выполнения, транзакционность, а также возможность динамически добавлять, удалять и изменять задачи без остановки планировщика. Именно благодаря этим качествам Quartz является де-факто стандартом для планирования в Java-экосистеме.
2. Установка
Quartz Scheduler распространяется через Maven Central. Для добавления в проект используйте систему сборки Maven или Gradle.
Maven
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<!-- Для использования JDBC JobStore (опционально) -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
Gradle
implementation 'org.quartz-scheduler:quartz:2.3.2'
implementation 'org.quartz-scheduler:quartz-jobs:2.3.2'
Если вы используете Spring Boot, достаточно добавить стартер spring-boot-starter-quartz, который уже включает Quartz и автоматически конфигурирует его.
3. Быстрый старт — минимальный рабочий пример
Создадим простое приложение, которое выводит "Hello, Quartz!" каждые 5 секунд.
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzQuickStart {
// 1. Создаём Job — класс, который будет выполнять работу
public static class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Hello, Quartz! Время: " + System.currentTimeMillis());
}
}
public static void main(String[] args) throws Exception {
// 2. Получаем планировщик из фабрики
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 3. Запускаем планировщик
scheduler.start();
// 4. Определяем JobDetail — описание задачи
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// 5. Создаём триггер — расписание: каждые 5 секунд, бесконечно
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();
// 6. Регистрируем задачу и триггер в планировщике
scheduler.scheduleJob(job, trigger);
// 7. Даём приложению поработать 30 секунд, затем завершаем
Thread.sleep(30000);
scheduler.shutdown();
}
}
Этот код демонстрирует основные шаги работы с Quartz: определение Job, создание Scheduler, настройка Trigger и запуск.
4. Основные методы/функции/классы
Рассмотрим 8 ключевых элементов API Quartz.
4.1. Scheduler.scheduleJob(JobDetail, Trigger)
Сигнатура: Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException
Что делает: Регистрирует задачу (JobDetail) и её триггер в планировщике. Возвращает время первого срабатывания триггера.
JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever())
.build();
scheduler.scheduleJob(job, trigger);
4.2. Scheduler.start()
Сигнатура: void start() throws SchedulerException
Что делает: Запускает планировщик. После вызова начинается обработка всех зарегистрированных триггеров.
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start(); // Планировщик начинает работу
4.3. Scheduler.shutdown()
Сигнатура: void shutdown() throws SchedulerException
Что делает: Останавливает планировщик. Все выполняющиеся задачи могут быть прерваны (в зависимости от параметра waitForJobsToComplete).
scheduler.shutdown(true); // true — дождаться завершения текущих задач
4.4. Scheduler.deleteJob(JobKey)
Сигнатура: boolean deleteJob(JobKey jobKey) throws SchedulerException
Что делает: Удаляет задачу и все связанные с ней триггеры из планировщика. Возвращает true, если задача была найдена и удалена.
JobKey jobKey = new JobKey("myJob", "group1");
boolean deleted = scheduler.deleteJob(jobKey);
if (deleted) {
System.out.println("Job deleted successfully");
}
4.5. Scheduler.rescheduleJob(TriggerKey, Trigger)
Сигнатура: Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException
Что делает: Заменяет существующий триггер новым. Полезно для изменения расписания без пересоздания задачи.
TriggerKey oldTriggerKey = new TriggerKey("myTrigger", "group1");
Trigger newTrigger = TriggerBuilder.newTrigger()
.withIdentity(oldTriggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 * * ?")) // Каждый день в 12:00
.build();
scheduler.rescheduleJob(oldTriggerKey, newTrigger);
4.6. JobBuilder.newJob(Class<? extends Job>)
Сигнатура: static JobBuilder newJob(Class<? extends Job> jobClass)
Что делает: Создаёт строитель (builder) для конфигурации JobDetail. Позволяет задать имя, группу, описание и данные задачи.
JobDetail job = JobBuilder.newJob(EmailJob.class)
.withIdentity("emailJob", "emailGroup")
.usingJobData("email", "user@example.com") // Передача параметров
.withDescription("Отправка email-уведомлений")
.build();
4.7. CronScheduleBuilder.cronSchedule(String)
Сигнатура: static CronScheduleBuilder cronSchedule(String cronExpression)
Что делает: Создаёт расписание на основе cron-выражения. Поддерживает стандартный синтаксис с 6 или 7 полями (секунды, минуты, часы, день месяца, месяц, день недели, год).
// Каждый понедельник, среду и пятницу в 18:30
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 30 18 ? * MON,WED,FRI *"))
.build();
4.8. JobExecutionContext.getMergedJobDataMap()
Сигнатура: JobDataMap getMergedJobDataMap()
Что делает: Возвращает объединённую карту данных из JobDetail и Trigger. Используется внутри метода execute() для получения параметров, переданных задаче.
public class ParameterizedJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getMergedJobDataMap();
String email = dataMap.getString("email");
int retryCount = dataMap.getInt("retryCount");
System.out.println("Отправка email на " + email + ", попытка " + retryCount);
}
}
5. Реальный практический пример (100-200 строк кода)
Разработаем систему для автоматической генерации и отправки еженедельных отчётов. Система будет:
- Использовать JDBC JobStore для сохранения расписания в базу данных H2 (для простоты примера).
- Поддерживать динамическое добавление новых задач через консоль.
- Логировать все события с помощью слушателей.
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.listeners.JobListenerSupport;
import org.quartz.listeners.TriggerListenerSupport;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ReportSchedulerSystem {
// Job для генерации отчёта
public static class ReportJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap data = context.getMergedJobDataMap();
String reportName = data.getString("reportName");
String recipient = data.getString("recipient");
System.out.println("[JOB] Генерация отчёта '" + reportName + "' для " + recipient);
// Имитация работы
try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println("[JOB] Отчёт '" + reportName + "' отправлен на " + recipient);
}
}
public static void main(String[] args) throws Exception {
// 1. Конфигурация с JDBC JobStore (используем H2 в памяти)
System.setProperty("org.quartz.properties", "quartz.properties");
// Создадим файл quartz.properties в resources:
// org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
// org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
// org.quartz.jobStore.dataSource = myDS
// org.quartz.dataSource.myDS.driver = org.h2.Driver
// org.quartz.dataSource.myDS.URL = jdbc:h2:mem:quartz
// org.quartz.dataSource.myDS.user = sa
// org.quartz.dataSource.myDS.password =
// org.quartz.jobStore.tablePrefix = QRTZ_
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
// 2. Добавляем слушатели для мониторинга
scheduler.getListenerManager().addJobListener(new JobListenerSupport() {
@Override
public String getName() { return "ReportJobListener"; }
@Override
public void jobToBeExecuted(JobExecutionContext context) {
System.out.println("[LISTENER] Задача " + context.getJobDetail().getKey() + " будет выполнена");
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
System.out.println("[LISTENER] Задача " + context.getJobDetail().getKey() + " завершена");
}
});
scheduler.getListenerManager().addTriggerListener(new TriggerListenerSupport() {
@Override
public String getName() { return "ReportTriggerListener"; }
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
System.out.println("[LISTENER] Триггер " + trigger.getKey() + " сработал");
}
});
// 3. Запускаем планировщик
scheduler.start();
// 4. Создаём первую задачу — еженедельный отчёт по понедельникам в 9:00
JobDetail weeklyReport = JobBuilder.newJob(ReportJob.class)
.withIdentity("weeklyReport", "reports")
.usingJobData("reportName", "Еженедельный отчёт по продажам")
.usingJobData("recipient", "sales@company.com")
.build();
Trigger weeklyTrigger = TriggerBuilder.newTrigger()
.withIdentity("weeklyTrigger", "reports")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 ? * MON *")) // Каждый понедельник в 9:00
.build();
scheduler.scheduleJob(weeklyReport, weeklyTrigger);
System.out.println("Добавлена задача: еженедельный отчёт");
// 5. Динамическое добавление задач через консоль
System.out.println("Введите команду: add <