Quartz Scheduler для Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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 <                    

Рекомендаций