Кварц: вираз Cron, який ніколи не буде виконаний


78

Я знаю , що є дублікат тут , що , ймовірно , це саме мій випадок, хоча б заслуговує кращого пояснення, які я спробую уявити тут.

Я працюю з веб-додатком Java, використовуючи контекст програми Spring. У цьому контексті я визначив заплановані завдання за допомогою кварцу. Ці завдання ініціюються cron, визначеним у файлі .properties.

Контекст Spring вбудований у війну, тоді як файл .properties знаходиться на сервері додатків (у цьому конкретному випадку - Tomcat).

Це чудово і дозволяє визначити різні крони відповідно до середовища (розробка, інтеграція, виробництво, ...).

Тепер, коли я запускаю цю програму локально на своєму комп’ютері, я не хочу, щоб ці завдання виконувались. Чи є спосіб написати вираз cron, який ніколи не спрацює?


Як і в іншому питанні, єдиним стандартним способом є розпочати команду #символом коментаря.
Бармар

1
Можливо, я не був зрозумілим: хоча в рамках Spring / Quartz використовується синтаксис cron, це не crontab: cron використовується в полі XML: <property name="cronExpression" value="<expression>" /> Ви не можете деактивувати завдання, коментуючи рядок. Хоча дякую. Я буду використовувати запропонований спосіб, вказуючи рік на майбутнє, комп’ютери, як ми знаємо, вони зникнуть.
Чоп

Очевидно, що кварцові вирази cron насправді не схожі на вирази cron в Unix, оскільки в Unix cron немає секунд або років. Тег cron, мабуть, не підходить для цього питання.
Бармар

Ви маєте рацію, я вже зустрічав деякі розбіжності. Я видалю тег 'cron'.
Чоп

Відповіді:


73

TL; DR

У Quartz 1 ви можете використовувати цей cron: 59 59 23 31 12 ? 2099(остання дата дії).
У Quartz 2 ви можете використовувати цей cron:0 0 0 1 1 ? 2200

Використання виразу далеко в майбутньому

Зробив кілька швидких тестів, використовуючи org.quartz.CronExpression.

String exp = "0 0 0 1 1 ? 3000";
boolean valid = CronExpression.isValidExpression(exp);
System.out.println(valid);
if (valid) {
    CronExpression cronExpression = new CronExpression(exp);
    System.out.println(cronExpression.getNextValidTimeAfter(new Date()));
}

Коли я це роблю String exp = "# 0 0 0 1 1 ?";, isValidтест повертається false.

Зі зразком, наведеним вище, результат виходить наступним:

true
null

Значення:

  • вираз дійсний;
  • немає майбутньої дати, яка відповідає цьому виразу.

Однак для того, щоб планувальник прийняв тригер cron, останній повинен відповідати даті в майбутньому.

Я спробував кілька років і зрозумів, що коли рік перевищує 2300, кварц, здається, вже не турбує (хоча я не знайшов згадки про максимальне значення року в документації на кварц 2 ). Це може бути більш чистий спосіб, але це задовольнить мої потреби на даний момент.

Отже, зрештою, я пропоную крон 0 0 0 1 1 ? 2200.

Кварцовий варіант 1

Зверніть увагу, що в кварці 1 2099 рік є останнім чинним роком . Тому ви можете адаптувати свій вираз cron, щоб використовувати пропозицію Мацей Матіс :59 59 23 31 12 ? 2099

Альтернатива: використання дати в минулому

Арно Денойель запропонував щось більш елегантне, що мій наведений вище тест підтверджує як правильний вираз: замість того, щоб обирати дату в далекому майбутньому, вибирайте її в далекому минулому:

0 0 0 1 1 ? 1970 (перший дійсний вираз згідно з кварцовою документацією).

Однак це рішення не працює.

hippofluff підкреслив, що кварц виявить вираз у минулому, більше ніколи не буде виконаний, і тому видасть виняток.

org.quartz.SchedulerException: Based on configured schedule, the given trigger will never fire.

Здається, це було в Кварці вже давно .

Вивчені уроки: тест не є надійним, як є

Це підкреслює слабкість мого тесту: якщо ви хочете протестувати a CronExpression, пам’ятайте, що він повинен мати nextValidTime1 . В іншому випадку планувальник, якому ви його передасте, просто відхилить його, згаданий вище виняток.

Я порадив би адаптувати тестовий код наступним чином:

String exp = "0 0 0 1 1 ? 3000";
boolean valid = CronExpression.isValidExpression(exp);
if (valid) {
    CronExpression cronExpression = new CronExpression(exp);
    valid = cronExpression.getNextValidTimeAfter(new Date()) != null;
}
System.out.println("Can I use <" + exp + ">? " + (valid ? "Go ahead!" : "This shall fail."));

Ось: не потрібно думати, просто прочитайте результати.


1 Це та частина, про яку я забув, перевіряючи рішення Арно, роблячи мене дурнем і доводячи, що мій тест не підтверджував мене.


3
Чому ні 0 0 0 1 1 ? 1970? Кварц відмовився б від цього?
Arnaud Denoyelle

5
Quartz 2.1.0 викидає SchedulerException "На основі налаштованого розкладу, заданий тригер ніколи не подаватиметься", і запуск не вдається в моєму додатку Grails при використанні пропозиції Арно
hippofluff

2
Не потрібно цього відчувати, дякую за оновлення відповіді! +1 :) FYI, дозволений час для дати в майбутньому становить 100 років. Все, що заплановано на понад 100 років у майбутньому, не вдасться запланувати. Не знаю, чому вони зупинились на 100 років, але якщо програма дійсно працює досить довго, щоб викликати це, я був би дуже вражений, ха-ха. Тим не менше, якщо ця технологія навіть відома через 100 років
hippofluff

2
Я перевірив код [1] для магічного значення року (2200 не вдається для мене) (javadoc [2] каже, що 2199 - максимум, підручник [3] - 2099 - максимум). Фактичний максимум: public static final int MAX_YEAR = Calendar.getInstance (). Get (Calendar.YEAR) + 100; Тож цього року максимум - 2116 Наступного року - максимум 2117 [1] fisheye.terracotta.org/browse/Quartz/trunk/quartz-core/src/main/… [2] quartz-scheduler.org/api/2.2. 1 / org / quartz / CronExpression.html [3] quartz-scheduler.org/documentation/quartz-2.x/tutorials/…
DelGurth

1
@Chop Так, дякую за це, оскільки він простий у використанні. Я підняв помилку в документації на теракоті, оскільки в даний час це бентежить, навіть у фактичному коді вони використовують різні перевірки максимального року.
DelGurth

42

Технічно допустимими значеннями для необов’язкового поля Кварцовий рік є 1970-2099, тому 2300 не є очікуваним значенням. Я припускаю, що ви насправді потрібно це зробити, і ваша версія Quartz намагається застосувати дійсний синтаксис cron (день 1-31, місяць 1-12 тощо).

На даний момент я використовую такий код у Resque-планувальнику для Rails, який приймає інформацію про розклад у перевіреному форматі crontab, для створення тестового завдання, що виконується лише вручну:

cron: "0 5 31 2 *"

Завдання буде терпляче чекати раннього ранку 31 лютого, перш ніж бігти. Для еквівалента в кварцовому крінтрігері спробуйте цей рядок або його варіант:

0 0 5 31 2 ?

Я дійсно зменшив рік так, що Кварц прийняв крон, який я йому дав. Ваше рішення справді досить елегантне. Дякую!
Чоп

9
Хам, нарешті, отримав нагоду перевірити цю пропозицію. Схоже, Кварц відмовляється від цього крон, оскільки виявляє, що він ніколи не буде виконаний ...
Чоп

Варто було пострілу, оскільки той самий метод, здавалося, добре працював у Rails. Ви можете використати, .startAt(startTime)щоб тригер думав, що він почався давно, а потім доручити йому запускати лише певний рік, який уже минув. Але це не настільки елегантно.
Eric Tjossem

1
0 5 31 2 *Один , здається, працює добре в Magento crons.
toon81

3
Це не працює з помилкоюInvalid cron expression "0 0 5 31 2 ?" led to runaway search for next trigger
Френкі Дрейк

25

Спробуйте це: 59 59 23 31 12 ? 2099


Чи можете ви пояснити мені, яким би був виграш, щоб використовувати його замість цього 0 0 0 1 1 ? 2200? Мій буде спрацьований у ніч на 2200 року Нея, тоді як ваш, як я розумію, пройде лише сто років до цього. Я помиляюся? Якщо ні, я думаю, що чим далі в майбутньому, тим краще, ви не згодні?
Чоп

11
Це останній дійсний кварцовий вираз, при цьому ваш кварц відмовляється починати роботу як мінімум з кварцу 1.6 та весни 2.5.6SEC3
Мацей Матіс

Я подивився документацію, і ви маєте рацію, це останній дійсний вираз. Проте 0 0 0 1 1 ? 2200працює з останньою версією Quartz. Проте було запропоновано розумніше рішення (див. Редакцію в моїй власній відповіді).
Чоп

7

Я знайшов це під час спроби вирішити подібну проблему - вимкнення виразу cron - але зіткнувся з тими ж проблемами, що вимагають дійсної дати майбутнього розкладу.

Я також зазнав проблем із використанням синтаксису 7 значень - не можу вказати рік у розкладі cron.

Тож я використав це: 0 0 3? 2 ПН ПН # 5

Наступного разу це буде виконано:

  1. Понеділок, 29 лютого 2044 р., 03:00
  2. Понеділок, 29 лютого 2072, 03:00
  3. Понеділок, 29 лютого 2112, 03:00
  4. Понеділок, 29 лютого 2140, 03:00
  5. Понеділок, 29 лютого 2168 р., 03:00

Отже, по суті, за будь-яких намірів це відключено. :)

Ага. Прокляття, це працюватиме лише для синтаксису планувальника кварцу - синтаксис Spring CronTrigger не дозволяє ПН # 5 п’ятий понеділок

Тож наступне найкраще - 0 0 3 29 2? який буде виконаний лише о 03:00 29 лютого (високосний рік)


Це мій улюблений обхідний шлях коли-небудь !!
Вартий

6

Якщо ви використовуєте вираз у @Scheduled(cron="")виразі (технічно не використовуючи кварц, а досить поширений у ті дні весни), ви не можете використовувати рішення з 7-річним роком у майбутньому, а такі варіанти:

  • Якщо ви використовуєте spring 5.1+ (springBoot 2.1+), просто використовуйте "${your.cron.prop:-}і не встановлюйте властивість відключати виконання - див. @Scheduled . Або встановіть для самого властивості значення "-" (обов’язково використовуйте лапки, якщо ви використовуєте yml).
  • Вимкніть bean / службу за допомогою @Scheduledметоду взагалі, наприклад, використовуючи @ConditionalOnProperty("my.scheduleproperty.active")анотацію і не встановлюючи властивість (або не встановлюючи для неї false)

2

Тепер, коли я запускаю цю програму локально на своєму комп’ютері, я не хочу, щоб ці завдання виконувались. Чи є спосіб написати вираз cron, який ніколи не спрацює?

Якщо ви хочете вимкнути планування на комп’ютері, у вас є кілька способів це зробити.

Спочатку ви можете перенести конфігурацію кварцу в @Profile конфігурацію основі і не вмикати цей профіль локально. Кварц взагалі не запускався, якщо профіль неактивний.

Альтернативою є налаштування Quartz для автоматичного запуску. Є SchedulerFactoryBean#setAutoStartup()те, що ви можете встановити, BeanPostProcessorзареєструвавшись у профілі розробника. Хоча цей потік досить старий, Spring Boot пропонує альтернативу, зареєструвавши SchedulerFactoryBeanCustomizerбоб, щоб робити те саме.


1

Привіт, ви можете спробувати це, воно ніколи не виконає ваш планувальник, просто пройде, як -у cron

 @Scheduled(cron = "${schedular.cron.expression}")

schedular.cron.expression=-
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.