Работа с шедулером в Java (Spring)

Недавно, в процессе работы, я столкнулся с задачей управления шедулерами в работающем приложении. У нас серверное приложение, и в конфигурационных файлах Spring мы указывали, какие задачи запускать по таймеру. Однако, далее появилась следующая задача: убирать из списка выполнения эти задачи или же менять cron-таймер, при этом не тормозя приложение.
В процессе гугления и чтения, я нашел, как это сделать. Всё оказалось гораздо проще, чем я думал. Но для того, чтобы это понять пришлось немного почитать.
Вероятно, эта статья будет полезна новичкам, но, возможно, и мастодонты почерпнут для себя что-то новое.

Используемые термины:

Шедулер — управляет таймерами запуска задач

Джоб — конкретная задача, запускаемая по таймеру

Триггер — условие выполнения задачи — задает временные рамки запуска задач и их выполнения

Соответственно, шедулер содержит в себе описания всех задачи и триггеров к ним.

В системе используется шедулер, который описан в конфигурационном файле системы (shedulers.xml):
<beans:bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <beans:property name="jobDetails">
        <beans:list>
            <beans:ref bean="reportCheckServiceJob" />
            <beans:ref bean="vacationApprovalAutoProcessServiceJob" />
            <beans:ref bean="plannedVacationServiceJob" />
            <beans:ref bean="employeeLdapServiceJob" />
            <beans:ref bean="oqProjectSyncServiceJob" />
        </beans:list>
    </beans:property>
    <beans:property name="triggers">
        <beans:list>
            <beans:ref bean="reportCheckServiceCronTrigger" />
            <beans:ref bean="vacationApprovalAutoProcessServiceCronTrigger" />
            <beans:ref bean="plannedVacationServiceCronTrigger" />
            <beans:ref bean="employeeLdapServiceCronTrigger" />
            <beans:ref bean="oqProjectSyncServiceCronTrigger" />
        </beans:list>
    </beans:property>
</beans:bean>


Пример джоб (и сервиса, метод которого он вызывает):
<beans:bean id="plannedVacationService"
            class="com.aplana.timesheet.service.PlannedVacationService" />
 
<beans:bean id="plannedVacationServiceJob"
            class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <beans:property name="targetObject" ref="plannedVacationService" />
    <beans:property name="targetMethod" value="service" />
</beans:bean>


Где targetObject указывает класс-сервис (бин), в котором содержится метод, который необходимо запускать по таймеру. В targetMethod указываем название метода.

Пример триггера:
<beans:bean id="plannedVacationServiceCronTrigger"
            class="org.springframework.scheduling.quartz.CronTriggerBean">
    <beans:property name="jobDetail" ref="plannedVacationServiceJob" />
    <beans:property name="cronExpression" value="${scheduler.plannedvacationcheck}" />
</beans:bean>


В этом триггере мы указываем джоб, который будем выполнять по триггеру (в данном случае это plannedVacationServiceJob, тоже бин), а так же укажем в формате cron режим и периодичность запуска задачи.

Теперь о том, как управлять шедулером.

Для управления этим шедулером, необходимо в контроллере (сервисе) подключить этот бин (в нашем случае это SchedulerFactoryBean).

У этой фабрики есть конкретный используемый шедулер, его можно получить методом getScheduler(). И уже через него управлять джобами, триггерами да и самими задачами.

Рассмотрим методы шедулера для остановки и запуска джобов:

pauseJob(String name, String Group) — остановить выполнение задачи шедулера в указанной группе джобов. Остановка происходит путём остановки соответствующего триггера (см. pauseTrigger)

resumeJob(String name, String Group) — возобновить выполнение задачи шедулера в указанной группе джобов. Восстановление происходит путём запуска соответствующего триггера (см. resumeTrigger)

pauseTrigger(String name, String Group) — останавливает триггер в соответствующей группе

resumeTrigger(String name, String Group) — возобновляет работу триггера в соответсвующей группе

pauseAll — останавливает все задачи шедулера (pauseJobs(String group) — только у конкретной группы)

resumeAll — возобновляет запуск всех задач шедулера (см. также resumeJobs)

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

Можно инжектить (@Autowired) напрямую триггеры и управлять их планировщиком cron. В примере это plannedVacationServiceCronTrigger, у которого мы меняем CronExpression. Или же можно обратиться к триггеру через шедулер, вызвав метод getTrigger(String name, String group).

    @Autowired
    @Qualifier("plannedVacationServiceCronTrigger")
    private CronTriggerBean plannedVacationServiceCronTrigger;
 
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
  
    public someMethod(){
 
        logger.info(plannedVacationServiceCronTrigger.getCronExpression());
        plannedVacationServiceCronTrigger.setCronExpression("*/1 * * * * ?");
 
        //schedulerFactoryBean.getScheduler().pauseJob("plannedVacationServiceJob", "DEFAULT");
        schedulerFactoryBean.getScheduler().pauseTrigger("plannedVacationServiceCronTrigger", "DEFAULT");
 
        for (String str : schedulerFactoryBean.getScheduler().getJobGroupNames()){
            logger.info(str);
        }
 
        for (String str : schedulerFactoryBean.getScheduler().getJobNames("DEFAULT")){
            logger.info(str);
        }
 
        logger.info(schedulerFactoryBean.getScheduler().getTrigger("plannedVacationServiceJob", "DEFAULT").getCronExpression());
    }



Что читать:
www.seostella.com/ru/article/2012/02/12/ispolzovanie-annotacii-autowired-v-spring-3.html
static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/scheduling.html
quartz-scheduler.org/api/2.1.0/org/quartz/Scheduler.html
ru.wikipedia.org/wiki/Cron
Метки:
Поделиться публикацией
Похожие публикации
Комментарии 10
  • 0
    Спасибо!

    Хотелось бы примеры:

    1. Как динамически добавлять джобы/убирать?
    2. Если я правильно помню, можно делать шедулер на основе или кварца, или джавовского таймера. Интересно понять, что в каком случае лучше
    3. Как дать шедулеру тред пул, чтобы ограничить число джобов, которые бегут одновременно
  • +1
    Я извиняюсь, но все-таки корректная транскрипция Scheduler — это «скедулер», а не «шедулер».
    • 0
      Оба варианта правильные. Первое — британский вариант, второе — американский.
      • 0
        Наоборот, «skɛ» — это американский вариант.
        • 0
          Я это и имел ввиду, что первый вариант — это который был в статье, «шедулер», а второй, тот, что предложил nerzhul — «скедулер».
          • 0
            Какая-то странная у вас логика… В смысле, комментируете, да так, что потом требуются объяснения, что вы имели ввиду. Жесть.
      • 0
        Послушал в гугл транслейт, через «ш» произносят.

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