Quarz: Cron-Ausdruck, der niemals ausgeführt wird

78

Ich weiß, dass es hier ein Duplikat gibt , was wahrscheinlich genau mein Fall ist, obwohl es eine bessere Erklärung verdient, die ich hier zu liefern versuchen werde.

Ich arbeite mit einer Java-Webanwendung unter Verwendung eines Spring-Anwendungskontexts. In diesem Zusammenhang habe ich geplante Jobs mit Quartz definiert. Diese Jobs werden von einem Cron ausgelöst, der in einer .properties-Datei definiert ist.

Der Spring-Kontext ist in den Krieg eingebettet, während sich die .properties-Datei auf dem Anwendungsserver befindet (in diesem speziellen Fall Tomcat).

Dies ist in Ordnung und ermöglicht die Definition verschiedener Crones je nach Umgebung (Entwicklung, Integration, Produktion, ...).

Wenn ich diese Anwendung jetzt lokal auf meinem eigenen Computer ausführe, möchte ich nicht, dass diese Jobs ausgeführt werden. Gibt es eine Möglichkeit, einen Cron-Ausdruck zu schreiben, der niemals ausgelöst wird?

Hacken
quelle
Wie bei dieser anderen Frage besteht die einzige Standardmethode darin, den Befehl mit dem #Kommentarzeichen zu beginnen .
Barmar
1
Möglicherweise war mir nicht klar: Obwohl das Spring / Quartz-Framework die Cron-Syntax verwendet, ist dies keine Crontab: Das Cron wird in einem XML-Feld verwendet: <property name="cronExpression" value="<expression>" /> Sie können den Job nicht deaktivieren, indem Sie die Zeile kommentieren. Trotzdem danke. Ich werde den vorgeschlagenen Weg verwenden, um ein Jahr anzugeben, das bisher in zukünftigen Computern, wie wir sie kennen, verschwunden sein wird.
Hieb
Anscheinend sind Quarz-Cron-Ausdrücke nicht wirklich wie Unix-Cron-Ausdrücke, da Unix-Cron keine Sekunden oder Jahre hat. Das Cron-Tag ist für diese Frage wahrscheinlich nicht geeignet.
Barmar
Sie haben Recht, ich habe bereits einige Unstimmigkeiten festgestellt. Ich werde das 'cron'-Tag entfernen.
Hieb

Antworten:

73

TL; DR

In Quarz 1 können Sie diesen Cron verwenden: 59 59 23 31 12 ? 2099(letztes gültiges Datum).
In Quartz 2 können Sie diesen Cron verwenden:0 0 0 1 1 ? 2200

Verwenden Sie einen Ausdruck weit in der Zukunft

Habe einige schnelle Tests mit gemacht 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()));
}

Wenn ich das tue String exp = "# 0 0 0 1 1 ?";, isValidkehrt der Test zurück false.

Mit dem oben angegebenen Beispiel ist die Ausgabe wie folgt:

true
null

Bedeutung:

  • der Ausdruck ist gültig;
  • Es gibt kein bevorstehendes Datum, das diesem Ausdruck entspricht.

Damit der Scheduler einen Cron-Trigger akzeptieren kann, muss dieser jedoch mit einem zukünftigen Datum übereinstimmen.

Ich habe es mehrere Jahre lang versucht und herausgefunden, dass Quarz, sobald das Jahr über 2300 liegt, nicht mehr zu stören scheint (obwohl ich keine Erwähnung eines Maximalwerts für das Jahr gefunden habe in der Dokumentation von Quarz 2 ). Es könnte einen saubereren Weg geben, dies zu tun, aber dies wird meine Bedürfnisse vorerst befriedigen.

Am Ende ist also der Cron, den ich vorschlage 0 0 0 1 1 ? 2200.

Variante Quarz 1

Beachten Sie, dass in Quarz 1 2099 das letzte gültige Jahr ist . Sie können daher Ihren Cron-Ausdruck anpassen, um den Vorschlag von Maciej Matys zu verwenden :59 59 23 31 12 ? 2099

Alternative: Verwenden eines Datums in der Vergangenheit

Arnaud Denoyelle schlug etwas Eleganteres vor, was mein Test oben als korrekten Ausdruck bestätigt: Anstatt ein Datum in ferner Zukunft zu wählen, wählen Sie es in einer fernen Vergangenheit:

0 0 0 1 1 ? 1970 (der erste gültige Ausdruck gemäß Quarzdokumentation).

Diese Lösung funktioniert jedoch nicht.

Hippofluff betonte, dass Quarz einen Ausdruck in der Vergangenheit erkennt , nie wieder ausgeführt wird und daher eine Ausnahme auslöst .

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

Dies scheint schon lange in Quarz zu sein .

Lektionen gelernt: Der Test ist nicht so narrensicher wie er ist

Dies unterstreicht eine Schwäche meines Tests: Wenn Sie a testen möchten CronExpression, denken SienextValidTime daran, dass es eine 1 haben muss . Andernfalls lehnt der Scheduler, an den Sie es übergeben, es einfach mit der oben genannten Ausnahme ab.

Ich würde empfehlen, den Testcode wie folgt anzupassen:

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."));

Los geht's: Sie müssen nicht nachdenken, lesen Sie einfach die Ausgabe.


1 Dies ist der Teil, den ich vergessen habe, als ich Arnauds Lösung getestet habe, um mich zum Narren zu machen und zu beweisen, dass mein Test nicht für mich geeignet war.

Hacken
quelle
3
Warum nicht 0 0 0 1 1 ? 1970? Würde Quarz es ablehnen?
Arnaud Denoyelle
5
Quartz 2.1.0 löst eine SchedulerException aus "Basierend auf dem konfigurierten Zeitplan wird der angegebene Trigger niemals gespeichert" und der Start in meiner Grails-App schlägt fehl, wenn Arnauds Vorschlag verwendet wird
Hippofluff
2
Das müssen Sie nicht spüren, danke für die Aktualisierung der Antwort! +1 :) Zu Ihrer Information, die zulässige Zeit für ein Datum in der Zukunft beträgt 100 Jahre. Alles, was in Zukunft über 100 Jahre alt sein soll, wird nicht geplant. Ich bin mir nicht sicher, warum sie sich für 100 Jahre entschieden haben, aber wenn eine Anwendung tatsächlich lange genug läuft, um auszulösen, dass ich sehr beeindruckt wäre, lol. Trotzdem, wenn diese Technologie in 100 Jahren überhaupt bekannt ist
Hippofluff
2
Ich habe den Code [1] auf den magischen Wert für das Jahr überprüft (2200 schlägt für mich fehl) (der Javadoc [2] sagt, dass 2199 maximal ist, das Tutorial [3] sagt, dass 2099 maximal ist). Das tatsächliche Maximum ist: public static final int MAX_YEAR = Calendar.getInstance (). Get (Calendar.YEAR) + 100; In diesem Jahr beträgt das Maximum 2116 Nächstes Jahr 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 Ja, danke dafür, da es einfach zu bedienen ist. Ich habe bei Terracotta einen Dokumentationsfehler gemeldet, da dieser derzeit verwirrend ist, selbst im eigentlichen Code werden unterschiedliche Max-Year-Checks verwendet.
DelGurth
41

Technisch gesehen sind gültige Werte für das optionale Quarzjahresfeld 1970-2099, sodass 2300 kein erwarteter Wert ist. Ich nehme dich wirklich an tun müssen und Ihre Version von Quartz versucht, eine gültige Cron-Syntax durchzusetzen (Tag 1-31, Monat 1-12 usw.).

Ich verwende derzeit den folgenden Code im Resque-Scheduler für Rails, der Zeitplaninformationen im validierten Crontab-Format akzeptiert, um einen Testjob zu erstellen, der nur manuell ausgeführt werden kann:

cron: "0 5 31 2 *"

Der Job wartet geduldig auf den frühen Morgen des 31. Februar, bevor er ausgeführt wird. Versuchen Sie für ein Äquivalent in Quarz-Crontrigger diese Linie oder eine Variante davon:

0 0 5 31 2 ?
Eric Tjossem
quelle
Ich habe das Jahr tatsächlich verkürzt, so dass Quartz den Cron akzeptierte, den ich ihm gab. Ihre Lösung ist in der Tat sehr elegant. Vielen Dank!
Chop
9
Hum, hatte endlich die Gelegenheit, diesen Vorschlag zu testen. Es scheint, dass Quarz diesen Cron ablehnt, da er feststellt, dass er niemals ausgeführt wird ...
Chop
War einen Versuch wert, da die gleiche Methode in Rails gut zu funktionieren schien. Möglicherweise möchten Sie .startAt(startTime)den Auslöser verwenden, um zu glauben, dass er vor langer Zeit gestartet wurde, und ihn dann anweisen, nur auf ein bestimmtes Jahr zu feuern, das bereits vergangen ist. Das ist aber nicht ganz so elegant.
Eric Tjossem
1
Der 0 5 31 2 *scheint in Magento Cron gut zu funktionieren.
Toon81
3
Dies funktioniert nicht mit einem FehlerInvalid cron expression "0 0 5 31 2 ?" led to runaway search for next trigger
Frankie Drake
25

Probieren Sie es aus: 59 59 23 31 12 ? 2099

Maciej Matys
quelle
Können Sie mir erklären, was der Gewinn wäre, wenn Sie es anstelle von verwenden würden 0 0 0 1 1 ? 2200? Meins würde an 2200 Ney Year's Eve ausgelöst werden, während deins erst hundert Jahre vorher sein wird, so wie ich es verstehe. Irre ich mich Wenn nicht, denke ich, je weiter es in Zukunft ist, desto besser, stimmst du nicht zu?
Kotelett
11
Dies ist der letzte gültige Quarz-Ausdruck, mit dem Ihr Quarz sich weigert, mindestens Quarz 1.6 und Frühjahr 2.5.6SEC3
Maciej Matys
Ich habe mir die Dokumentation angesehen und Sie haben Recht, dies ist der letzte gültige Ausdruck. Funktioniert dennoch 0 0 0 1 1 ? 2200mit der neuesten Version von Quartz. Es wurde jedoch eine intelligentere Lösung vorgeschlagen (siehe die Bearbeitung in meiner eigenen Antwort).
Chop
7

Ich fand dies, als ich versuchte, ein ähnliches Problem zu lösen - das Deaktivieren eines Cron-Ausdrucks -, stieß jedoch auf die gleichen Probleme, ein gültiges zukünftiges Planungsdatum zu benötigen.

Ich habe auch Probleme mit der 7-Wert-Syntax - kann kein Jahr im Cron-Zeitplan angeben.

Also habe ich folgendes benutzt: 0 0 3? 2 MON # 5

Das nächste Mal, wenn dies ausgeführt wird, sind:

  1. Montag, 29. Februar 2044, 3:00 Uhr
  2. Montag, 29. Februar 2072, 3:00 Uhr
  3. Montag, 29. Februar 2112, 3:00 Uhr
  4. Montag, 29. Februar 2140, 3:00 Uhr
  5. Montag, 29. Februar 2168, 3:00 Uhr

Im Grunde genommen ist es also in jeder Hinsicht deaktiviert. :) :)

Ah. Flüche, dies funktioniert nur für die Quarz-Scheduler-Syntax - Die Spring CronTrigger-Syntax erlaubt MON # 5 für den fünften Montag nicht

Das nächstbeste ist also 0 0 3 29 2? die erst am 29. Februar um 3 Uhr morgens ausgeführt wird (Schaltjahre)

mrmoosehead
quelle
Dies ist meine Lieblingsumgehung überhaupt !! Ein Abzeichen wert :-)
drizin
6

Wenn Sie den Ausdruck in einem @Scheduled(cron="")Ausdruck verwenden (technisch gesehen wird kein Quarz verwendet, sondern eher im Frühling verwendet), können Sie nicht die 7-Feld-Lösung für das zukünftige Jahr verwenden, sondern die folgenden Optionen:

  • Wenn Sie Spring 5.1+ (springBoot 2.1+) verwenden, verwenden Sie einfach "${your.cron.prop:-}die Eigenschaft und setzen Sie sie nicht so, dass die Ausführung deaktiviert wird - siehe @Scheduled . Oder setzen Sie die Eigenschaft selbst auf "-" (stellen Sie sicher, dass Sie Anführungszeichen verwenden, wenn Sie eine yml verwenden).
  • Deaktivieren Sie die Bean / den Dienst mit der @ScheduledMethode insgesamt, z. B. indem Sie eine @ConditionalOnProperty("my.scheduleproperty.active")Anmerkung verwenden und die Eigenschaft nicht festlegen (oder auf festlegen false).
icyerasor
quelle
2

Wenn ich diese Anwendung jetzt lokal auf meinem eigenen Computer ausführe, möchte ich nicht, dass diese Jobs ausgeführt werden. Gibt es eine Möglichkeit, einen Cron-Ausdruck zu schreiben, der niemals ausgelöst wird?

Wenn Sie die Zeitplanung auf Ihrem Computer deaktivieren möchten, haben Sie verschiedene Möglichkeiten, dies zu erreichen.

Zuerst können Sie die Konfiguration von Quartz auf a verschieben @Profile basierte Konfiguration verschieben und dieses Profil nicht lokal aktivieren. Quarz würde überhaupt nicht starten, wenn das Profil nicht aktiv ist.

Eine Alternative besteht darin, Quartz so zu konfigurieren, dass es nicht automatisch startet. Es gibt eine SchedulerFactoryBean#setAutoStartup(), die Sie BeanPostProcessorin einem Entwicklerprofil registrieren können. Während dieser Thread ziemlich alt ist, bietet Spring Boot eine Alternative, indem eine SchedulerFactoryBeanCustomizerBean registriert wird , um dasselbe zu tun.

Stephane Nicoll
quelle
1

Hallo, Sie können dies versuchen, es wird niemals Ihren Zeitplan ausführen, nur wie -in Cron

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

schedular.cron.expression=-
Pallav Chanana
quelle