Кварц: вираз 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.