Vous êtes sur la page 1sur 16

Edited by Foxit PDF Editor

Copyright (c) by Foxit Software Company, 2004


For Evaluation Only.

Planification de tâches en JAVA

Table des matières

1 Introduction ...................................................................................................................... 1
2 Planification de tâches avec le JDK ................................................................................ 2
3 Planification de tâches avec Quartz................................................................................ 4
3.1 Premiers pas ....................................... ........................................................................... 4
3.1.1 Installation / Configuration ................................................................................ 4
3.1.2 Démarrage .......................................................................................................... 4
3.1.3 Définition d’un Job............................................................................................. 5
3.1.4 Définition d’un Job avec conservation d’état..................................................... 5
3.1.5 Définition d’un Trigger ...................................................................................... 5
3.1.6 Echec d’activation d’un trigger (misfire) : ......................................................... 7
3.1.7 Les listeners........................................................................................................ 7
3.2 Gestion des Jobs et Triggers....................................................................................... 8
3.3 Services techniques .................................................................................................... 9
3.3.1 Gestion des exécutions concurrentes.................................................................. 9
3.3.2 Persistance .......................................................................................................... 9
3.3.3 Séparation Client/Serveur avec RMI................................................................ 10
3.3.4 Clustering ......................................................................................................... 12
3.3.5 Plugins .............................................................................................................. 13
3.4 Quelques Jobs utiles fournis avec Quartz : .............................................................. 13
3.5 Démarrer le scheduler en même temps qu’un serveur web...................................... 15
4 Conclusion....................................................................................................................... 15
5 Références ....................................................................................................................... 16

1 Introduction
Beaucoup d' applications d'entreprise nécessitent des traitements
souvent longs et coûteux en ressources système. Lorsque ces
traitements ne nécessitent pas d'intéraction avec l'utilisateur, leur
déclenchement peut être différé sur une période de charge faible,
afin de ne pas détériorer les temps de réponse. Depuis la version
1.4, java permet d'effectuer des planifications de tâche simples.
Cependant les applications d'entreprise exigent des possibilités de
reprise après panne, haute disponibilité, etc. C'est ce que propose
Quartz, une librairie open-source.

Mots clés : Planification de tâche (Job-Scheduling), Java, Timer, Thread, Quartz


2 Planification de tâches avec le JDK
Remarque : Ce tutorial s’est basé sur la version 1.4.2 du JDK.

Depuis la version 1.3, l’API standard de Java propose un système de planification de tâches
basique au travers des classes java.util.Timer et java.util.TimerTask [1]. La classe Timer
représente le scheduler et TimerTask une tâche à exécuter. On définit une tâche avec une
classe qui dérive de TimerTask et implémente la méthode run(). Une instance de la tâche est
ensuite passée au Timer en spécifiant des paramètres de planification. Le timer exécute
ensuite la méthode run() à la date programmée.

L’exemple suivant permettra de se familiariser avec l’API. On définit une tâche MyTask qui
attend aléatoirement entre 1000 et 2500 ms. Cette tâche a été planifiée pour être exécutée
toutes les deux secondes, trois secondes après le démarrage du programme.

class MyTask extends TimerTask {


Random r=new Random();
public void run() {
try {
System.out.println("DEBUT");
int t = 1000+r.nextInt(1500);
Thread.sleep(t);
System.out.println("FIN : "+t);
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}

public class Test {

public static void main(String[] args) {


Timer t = new Timer();
GregorianCalendar gc = new GregorianCalendar();
gc.add(Calendar.SECOND, 3);
t.scheduleAtFixedRate(new MyTask(), gc.getTime(), 2000);
}
}

La classe Timer possède deux constructeurs qui permettent de créer un timer en mode blocant
ou en mode démon. Pour rappel, un programme java se termine une fois que tous les threads
non démons sont terminés.
-Le constructeur par défaut crée un timer blocant. C’est le cas de notre programme d’exemple
qui attend donc l’arrêt de la JVM pour se terminer (CTRL-C).
-Le constructeur avec argument crée un timer démon. Celui-ci se termine en même temps que
le programme principal. Pour l’utiliser dans notre exemple, il faut mettre en attente le thread
principal avec ajoutant par exemple Thread.sleep(…) à la fin du bloc main, afin que le timer
puisse lancer ses tâches. Le lancement d’un autre thread blocant comme une frame swing est
une autre possibilité.

Une fois le timer lancé, on crée une planification en spécifiant une date de début et la période
de répétitions. Attention : certaines planifications comme « tous les mois » ne seront pas
possibles car la période entre deux mois n’est jamais la même…
Il existe deux types de planification :
Timer.schedule(…) respecte les période de répétitions. La date de prochain traitement est
calculée en ajoutant la période à la date du dernier traitement. Si cette date n’est pas respectée
(par ex : le garbage-collector ou l’accès à une ressource a ralenti la tâche), un retard apparaît
et s’accumule au précédent.
Timer.scheduleAtFixedRate(…) essaye de respecter les dates d’exécution. La date de prochain
traitement est calculée par rapport à la date du premier traitement. Ainsi si une tâche a pris du
retard, les tâches suivantes peuvent rattraper ce retard en s’exécutant immédiatement après
leur précédente.

Le Timer, comme tous les scheduler ne peut pas empêcher l’apparition des retards. Mais
généralement les schedulers proposent des solutions pour les minimiser ou les contourner en
amortissant le retard d’une tâche sur les suivantes, quitte à ne pas respecter un intervalle
défini.
De plus la probabilité et le temps moyen des retards augmente avec le nombre de tâche,
surtout si plusieurs tâches sont programmée en même temps. Et là le Timer n’est pas adapté.
La raison réside dans son implémentation. Le Timer dispose d’une file d’attente de tâches
ordonnée par date croissante de prochain démarrage et d’un thread de traitement.
L’algorithme implémenté est simple : le thread se met en attente de la première tâche de la
file. Lorsque la date d’exécution est atteinte, le thread appelle la méthode run(), met à jour la
prochaine date d’exécution de la tâche et se remet en attente.
La présence d’un seul thread de traitement interdit d’optimiser les exécutions concurrentes de
tâches. Malheureusement ce mode d’utilisation est fréquent sur les applications d’entreprise,
généralement distribuées. Dans une application distribuée plusieurs tâches peuvent facilement
s’exécuter en parallèle pour optimiser l’utilisation des ressources. Une tâche peut effectuer un
calcul tandis qu’une autre effectue des requêtes sur une base de données, et une autre effectue
un transfert de fichier… Le Timer n’est donc pas optimal dans ce genre d’application, mais tel
n’est pas son but. D’autant qu’il existe une spécification pour un Timer « j2ee » géré par un
conteneur de serveur d’application.

D’autres points font du Timer un composant pour des applications standards et non
« entreprise » :
-pas de planification sophistiquée.
-pas de persistance des tâches
-pas de système de gestion des tâches évolué
Cependant les cas d’utilisation du timer sont multiples et ce dernier reste intéressant pour sa
grande simplicité et son intégration dans l’API standard (. Des exemples ? la reconnexion à
intervalle régulier d’un client FTP ou l’observation de la modification d’un fichier de
paramétrage.
Comme d’habitude, tout dépend du besoin de votre application…
3 Planification de tâches avec Quartz
Remarque : Ce tutorial s’est basé sur la version 1.4.5 de Quartz.

Il existe plusieurs schedulers disponibles pour la plateforme java mais les plus intéressants
sont Quartz et Flux. Flux est un scheduler commercial très complet, livré avec des outils de
conception et de monitoring, qui permet même de gérer des processus workflow. Quartz ne va
pas jusque là mais dispose de nombreuses fonctionnalités qui conviendront à la plupart des
applications métier. Quartz fait partie du projet OpenSymphony qui propose des composants
orientés entreprise pour la plateforme J2EE La licence de Quartz dérive et est entièrement
compatible avec Apache Software License définie par Apache Software Foundation [2] . Il est
open-source, peut être redistribué avec modification des sources, et libre d’utilisation dans des
projets commerciaux. C’est pour cette raison que nous allons le découvrir.

3.1 Tutorial de base.


Ce tutorial permet de découvrir les composants de base de Quartz.

3.1.1 Installation / Configuration


Dans un premier temps, je vous laisse télécharger la dernière version de Quartz [3] !
Quartz est composé d’une librairie principale et de librairies annexes issues d’autres projets
open-source comme apache commons-xxx. Ces dernières ne sont pas toutes utiles pour
l’instant :

Librairies de base nécessaire lib/quartz.jar


lib/commons-logging.jar
NB : Ces librairies sont à inclure dans le classpath de l’application.

3.1.2 Démarrage
Il existe principalement trois types d’objets : le Scheduler, les Jobs et les Triggers. Un Job
(travail) représente une tâche à exécuter, un Trigger (déclencheur) représente le mécanisme de
planification d’une tâche. Concernant les relations, un job peut être utilisé par plusieurs
trigger, mais un trigger n’est associé qu’à un seul job.

En démarrant le scheduler, celui-ci se mettra immédiatement en attente des jobs. Pour cela on
fait d’abords appel à la factory qui permet d’instancier le scheduler, puis on invoque la
méthode start() sur l’instance obtenue. Le type de scheduler est déterminé par la factory en
fonction des services techniques décrit dans un fichier de propriété, cependant ce fichier n’est
pas nécessaire si on souhaite travailler avec le scheduler par défaut. Dans notre cas, les
services comme le fail-over et le clustering seront désactivés (voir §3.3.4).

SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();


Scheduler sched = schedFact.getScheduler();
sched.start();
3.1.3 Définition d’un Job

D’abords on crée une classe implémentant l’interface Job :


public class RapportsVentesJob implements Job {
public void execute(JobExecutionContext arg0) throws
JobExecutionException {
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new JobExecutionException(e);
}
}
}
Les instances des jobs sont gérées par Quartz, il n’est pas possible de les instancier
directement. A la place, on instancie un JobDetail qui décrit un Job
JobDetail jobDetail = new JobDetail("myJob",
Scheduler.DEFAULT_GROUP,
RapportsVentesJob.class);

Les paramètres minimums du JobDetail sont :


-définition du nom du job (qui permet d’être identifié par le scheduler)
-définition du groupe d’appartenance (pour les opérations de maintenance groupées comme la
suppression ou la mise en pause d’un ensemble de tâches)
-la classe du job à exécuter.

Il est possible de passer des données à un job en utilisant un JobDataMap. Cet objet est un
tableau associatif qui sera disponible dans le contexte d’exécution du Job (méthode execute)
JobDataMap map = new JobDataMap();
map.put(“userId”,"155-123587");
jobDetail.setJobDataMap(map);

public void execute(JobExecutionContext context) throws


JobExecutionException {
JobDataMap map = context.getJobDetail().getJobDataMap();
map.getString("userId");
}

3.1.4 Définition d’un Job avec conservation d’état


L’état du contexte d’exécution d’un job est conservé pour l’exécution suivante au sein d’une
même planification. Autrement dit, les données de JobDataMap sont conservées entre chaque
top du Trigger. Côté implémentation il suffit que le Job implémente l’interface StatefulJob.
Remarque : les exécutions concurrentes de StatefulJob ne sont pas possibles et sont lancées en
séquence si plusieurs Trigger se déclenchent en même temps.

3.1.5 Définition d’un Trigger


Il existe actuellement deux types de Trigger :

SimpleTrigger : ce trigger simple permet des planifications d’exécution « immédiate et


unique » ou récurrente avec période fixe, avec nombre de répétition limitée ou non. C’est à
peu près l’équivalent du Timer de java.
Les paramètres minimums sont :
-définition du nom du trigger (qui permet d’être identifié par le scheduler)
-définition du groupe d’appartenance (pour les opérations de maintenance groupées)
Ensuite différents constructeurs permettent de définir:
- la date de début
- le nombre de répétition
- la période

SimpleTrigger trigger = new SimpleTrigger("myTrigger",


Scheduler.DEFAULT_GROUP,
5,
2000);

CronTrigger : ce trigger permet des planifications basées sur les jours du calendrier. Il utilise
la syntaxe des expressions cron d’Unix.

Une expression cron est un ensemble de six champs obligatoires plus un facultatif, séparés par
des espaces, et pouvant êtres associés à des caractères spéciaux:

Champs Valeurs Caractères spéciaux


Seconde [0-59] ,-/*
Minute [0-59] ,-/*
Heure [0-23] ,-/*
Jour du mois [1-31] ,-/*?LWC
Mois [1-12] ou {JAN,MAY,…} ,-/*
Jour de la semaine [1-7] ou {MON,WED,…} ,-/*?L#C
année vide ou [1970-2099] ,-/*

Signification des caractères spéciaux :


, séparateur de valeurs par ex : 10, 11,12 ou JAN, MAR
- intervalle entre deux valeurs par ex : 10-14 signifie 10, 11, 12, 13,14
/ incrément d’une valeur de départ par ex : 0/15 correspond aux valeurs 0, 15, 30, 45.
* toutes les valeurs
? permet de distinguer l’utilisation du jour du mois du jour de la semaine (c’est soit l’un
soit l’autre)
L signifie le dernier du mois ou de la semaine, par ex : 6L
W signifie le plus proche jour de la semaine hors week-end, par ex : 10W signifie que si
le 10 est un dimanche, alors l’exécution se fera le lundi.
# permet de spécifier le nième jour de la semaine dans le mois, par ex : LUN#1 signifie
le premier lundi du mois.
C utilise le calendrier (Calendar, voir plus loin) pour définir les jours à exclure

Exemples :
« Tous les jours du lundi au vendredi à 08h00 » se traduit par « 0 0 8 ? * MON-FRI »
« Tous les derniers vendredi du mois à 10h15 » se traduit par « 0 15 10 ? * 6L »

CronTrigger trigger = new CronTrigger("myTrigger",


Scheduler.DEFAULT_GROUP,
"0/3 * * * * ?");
Calendar : interface permettant de spécifier les jours à exclure d’une planification. On utilise
Calendar si la planification est basées sur des règles métiers qui ne peuvent pas être
exprimées entièrement par une expression cron, par exemple les jours fériés ou les jours de
repos. Il existe des implémentations tel que HolidayCalendar qui permet d’exclure des jours
fériés ou WeeklyCalendar qui permet d’exclure certains jours de la semaine.
WeeklyCalendar c = new WeeklyCalendar();
c.setDayExcluded(java.util.Calendar.MONDAY, true);

trigger.setCalendarName("cal1");
sched.addCalendar("cal1", c, true, true);

Enfin, n’oublions pas l’enregistrement de la planification de notre job !

sched.scheduleJob(jobDetail, trigger) ;

3.1.6 Echec d’activation d’un trigger (misfire) :


Bien que limités par l’utilisation du pool de thread, les retards peuvent toujours apparaître. On
s’en serait douté, Quartz gère les retards de manière plus sophistiquée que le Timer.

Lorsqu’un trigger ne peut pas traiter un job à l’heure prévue, le trigger passe dans l’état
« misfired ». Ce cas se produit lorsque qu’aucun thread du pool n’est disponible (jobs trop
longs par exemple), ou que le scheduler, lancé en tant que serveur distant, est injoignable.
Lorsqu’un Trigger passe dans l’état « misfired », le scheduler peut tenter d’effectuer une
action tel que relancer le job immédiatement. C’est semble-t-il la politique par défaut, qui
correspond au comportement du Timer du JDK. Il existe cependant d’autres actions possibles
et ce pour chaque type de trigger. On spécifie l’action via la méthode
Trigger.setMisfireInstruction(int).

Actions d’un SimpleTrigger :


MISFIRE_INSTRUCTION_FIRE_NOW
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

Actions d’un CronTrigger :


MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
MISFIRE_INSTRUCTION_DO_NOTHING

Les noms d’actions renseignent déjà d’eux-même, mais pour plus de détail, cf la javadoc !

On peut également permettre un délai de retard au-delà duquel le Trigger passe dans l’état
« misfired ». Le délai de retard par défaut est de 60 secondes et peut se configurer (voir § 3.3).

3.1.7 Les listeners


Quartz permet au programme client d’être notifié des actions importantes effectuées par les
différents composants du scheduler au travers des Listeners. Pour cela le client doit
enregistrer auprès du composant à écouter une instance du type de listener qui lui est associé.
Il existe trois types de Listener :
JobListener : permet d’être notifié du début et de la fin d’une exécution, et si celle-ci s’est
terminée avec ou sans erreur. A chaque fois, le contexte d’exécution est renvoyé.
TriggerListener : permet d’être notifié de l’envoi d’un ordre d’exécution ou de l’impossibilité
de l’envoyer (misfire).
SchedulerListener : permet d’être notifié entre autres de la création, suppression ou mise en
attente de la planification d’une tâche.
Remarque : les listeners ne servent pas à créer des logs (il existe un plugin qui fait cela
simplement) mais ils peuvent servir à mettre à jour l’application cliente, à notifier par mail ou
par SMS un utilisateur, une équipe de maintenance, … . L’application cliente peut définir des
tables de BDD où le listener ira mettre à jour l’état d’exécution des jobs, consultable depuis
l’application, avec la possibilité en cas d’erreur de les relancer. Cela évite à l’application
d’être trop dépendante du scheduler.

Exemple :
class RapportsVentesJobListener implements JobListener {
public String getName() {
return "rapportsVentesListener";
}
public void jobExecutionVetoed(JobExecutionContext context) {
}
public void jobToBeExecuted(JobExecutionContext context) {
System.out.println("mise à jour du SI client : le job va être
exécuté");
}
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException) {
System.out.println("mise à jour du SI client : le job s'est
exécuté");
if(jobException==null) {
System.out.println("ENVOI mail succès");
} else {
System.out.println("ENVOI mail échec");
System.out.println(jobException);
}
}
}

sched.addJobListener(new RapportsVentesJobListener());

jobDetail.addJobListener("rapportsVentesListener");

3.2 Gestion des Jobs et Triggers


Supprimer un job et les planifications (triggers) associés :
sched.deleteJob("myJob", Scheduler.DEFAULT_GROUP);
Supprimer une planification (trigger) :
sched.unscheduleJob("myTrigger", Scheduler.DEFAULT_GROUP);
Replanifier un job (remplace l’ancienne planification par une nouvelle):
sched.rescheduleJob("myTrigger", Scheduler.DEFAULT_GROUP, trigger);

Mettre en attente/ Reprendre des jobs et des triggers :


sched.pauseJobGroup(Scheduler.DEFAULT_GROUP);
Thread.sleep(20000); pause de vingt secondes.
sched.resumeJobGroup(Scheduler.DEFAULT_GROUP);
Dans cet exemple, il est probable que le trigger soit passé dans l’état « misfired » du fait du
temps d’attente. Par défaut tous les jobs qui auraient du s’exécuter dans l’intervalle de pause
vont s’exécuter immédiatement (§3.1.6)
3.3 Services techniques
Quartz propose un ensemble de services techniques comme la gestion des threads, la
persistance ou le clustering. Ces services sont paramétrables dans un fichier de propriétés java
Exemple de fichier quartz.properties
#==========================================================================
# Configure Main Scheduler Properties
#==========================================================================

org.quartz.scheduler.instanceName = SchedulerDeTest
org.quartz.scheduler.instanceId = scheduler1

#==========================================================================
# Configure ThreadPool
#==========================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = -1
org.quartz.threadPool.threadPriority = 5

#==========================================================================
# Configure JobStore
#==========================================================================

org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

Pour informer quartz qu’il doit utiliser un fichier et non le paramétrage par défaut, il suffit de
renseigner son emplacement dans une variable système de la JVM au démarrage :

java -Dorg.quartz.properties=conf/quartz.properties …

3.3.1 Gestion des exécutions concurrentes


Quartz implémente un pool de threads pour gérer les ressources en thread lors des exécutions
concurrentes. Lors qu’un trigger lance l’exécution d’un job, celui-ci prend un thread dans le
pool. Lorsqu’aucun thread n’est disponible, le job est mis en attente. Le nombre de threads
dépend du besoin de l’application, des ressources systèmes disponibles et se détermine
généralement expérimentalement. Par exemple si plusieurs jobs sont lancés par minutes, un
pool de vingt threads ou plus pourrait être nécesaire. A l’inverse si quelques jobs sont lancés
de manière espacée dans une journée, un seul thread suffira. De plus il faut savoir que dans un
serveur d’application, le pool de thread n’est pas géré par le conteneur. Il doit être cependant
possible de programmer un pool de Thread réalisant cela…

org.quartz.threadPool.threadCount = 20

Remarque : pour ne pas utiliser de pool, il suffit de donner la valeur -1.

3.3.2 Persistance
La gestion du cycle de vie des jobs et des triggers est réalisée par le JobStore. Par défaut, ou si
on le paramètre ainsi, Quartz utilise un JobStore non persistant avec la classe
org.quartz.simpl.RAMJobStore. Par conséquent si l’application se plante, les jobs sont perdus.
La solution est d’utiliser un JobStore persistant dans un SGBD. Quartz supporte la plupart des
SGBD. Cela permet de garantir la reprise après panne (fail-over), en contrepartie les
performances du scheduler sont diminuées du fait des requêtes SGBD, mais cela est
généralement négligeable car ils existent souvent d’autres ralentissements dans une
application.

Exemple d’un JobStore persistent dans une base MySQL :


#==========================================================================
# Configure JobStore
#==========================================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass =
org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = QRTZ_DS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.isClustered = false

-driverDelegateClass définit la classe de mapping entre le JobStore et SGBD. Ici j’ai choisi le
mapping standard car j’utilise MySQL. Les autres SGBD possèdent leur propre mapping qui
sont référencés dans la documentation de Quartz.
-tablePrefix permet de distinguer les tables Quartz des autres, si vous décider d’installer les
tables dans une base existante.
-dataSource référence la description de la datasource (voir ci-dessous)
- misfireThreshold définit le délai de dépassement par rapport à la date planifiée au-delà
duquel un trigger passe dans l’état misfired.
- isClustered active ou non le mode cluster (voir plus loin)

Remarque : Les scripts de création des tables Quartz se trouvent dans répertoire docs/dbTables.

Définition de la datasource associée :

#==========================================================================
# Configure Datasources
#==========================================================================
org.quartz.dataSource.myDS.driver = org.gjt.mm.mysql.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost/quartz
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password =
org.quartz.dataSource.myDS.maxConnections = 5
org.quartz.dataSource.myDS.validationQuery = select lock_name from
qrtz_locks where lock_name = 'TRIGGER_ACCESS';

Librairies supplémentaires nécessaires lib/commons-collections.jar


lib/commons-dbcp-1.1.jar
lib/commons-pool-1.1.jar
mm.mysql-2.0.2-bin.jar

3.3.3 Séparation Client/Serveur avec RMI.


Jusqu’à maintenant notre scheduler fonctionnait dans la même JVM que le client. Mais
Quartz est aussi prévu pour faire fonctionner le scheduler en tant que serveur distant. Ce mode
est utile si on souhaite partager un scheduler entre plusieurs applications clientes ou alléger la
charge d’un client.
Exemple de serveur :
public class TestServer {

public static void main(String[] args) {

if(System.getSecurityManager() != null) {
System.setSecurityManager(
new java.rmi.RMISecurityManager()
);
}

try {
SchedulerFactory schedFact = new StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
} catch (SchedulerException se) {
se.printStackTrace();
}
}
}

Configuration quartz du serveur : Il faut ajouter ces paramètres en plus du pool de thread et du
jobStore
org.quartz.scheduler.instanceName = Sched1
org.quartz.scheduler.rmi.export = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099
org.quartz.scheduler.rmi.createRegistry = true

Rmi.policy : Dans notre exemple ce script accorde toutes les permissions (tous les clients sont
acceptés et font ce qu’ils veulent…)
grant {
permission java.security.AllPermission;
};

Script de lancement du serveur :


@SET QRTZ=d:\program\quartz-1.4.5

@SET QRTZ_CP=.;%QRTZ%\lib\commons-logging.jar;%QRTZ%\lib\commons-
collections.jar;%QRTZ%\lib\commons-dbcp-1.1.jar;%QRTZ%\lib\commons-pool-
1.1.jar;%QRTZ%\lib\log4j.jar;%QRTZ%\lib\jdbc2_0-
stdext.jar;%QRTZ%\lib\quartz.jar

@SET RMI_CODEBASE=file:/d:/program/quartz-1.4.5/lib/quartz.jar

java -cp %QRTZ_CP% -Djava.rmi.server.codebase=%RMI_CODEBASE% -


Djava.security.policy=java.policy -
Dorg.quartz.properties=quartzServer.properties
com.developpez.scheduletest.quartz.TestServer

Exemple de client :
public class TestClient {

public static void main(String[] args) {


try {
SchedulerFactory schedFact =
new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
JobDetail jobDetail = new JobDetail(
"myJob",
Scheduler.DEFAULT_GROUP,
RapportsVentesJob.class
);

CronTrigger trigger = new CronTrigger(


"myTrigger",
Scheduler.DEFAULT_GROUP
);
trigger.setCronExpression(
"0/3 * * * * ?"
);
sched.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Configuration quartz du client : seuls ces parameters sont utiles.


org.quartz.scheduler.instanceName = Sched1
org.quartz.scheduler.rmi.proxy = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099

Script de lancement du client :


@SET QRTZ=d:\program\quartz-1.4.5

@SET QRTZ_CP=.;%QRTZ%\lib\commons-logging.jar;%QRTZ%\lib\commons-
collections.jar;%QRTZ%\lib\commons-dbcp-1.1.jar;%QRTZ%\lib\commons-pool-
1.1.jar;%QRTZ%\lib\log4j.jar;%QRTZ%\lib\jdbc2_0-
stdext.jar;%QRTZ%\lib\quartz.jar
java -cp %QRTZ_CP% -Dorg.quartz.properties=remoteClient.properties
com.developpez.scheduletest.quartz.TestClient

Remarque : En client/serveur, le job est exécuté dans la JVM du serveur, il faudra donc
veiller à ce que le scheduler dispose des classes nécessaire à l’exécution…

3.3.4 Clustering
Le clustering permet de créer un super-scheduler réparti sur des machines différentes.
Le traitement d’un job s’effectuera sur la première instance de scheduler disponible dont au
moins un thread est disponible. De même si un job s’exécute sur une instance et que celle-ci
plante, il est possible de paramétrer la récupération du job sur une instance disponible (voir
l’exemple ClusterTest de Quartz) Cela permet donc de garantir une haute disponibilité de
traitement des tâches, une reprise après panne améliorée de même qu’un équilibrage de
charge, lorsque les ressources d’une machine seule ne suffisent plus (trop de threads ou trop
de mémoire utilisée).
Prérequis :
Il faut utiliser un JobStore persistant, pointant sur une datasource partagée par chaque
instance.
Les machines du clusters doivent être synchrones (utiliser un serveur de temps)
Configuration :
Les identifiants d’instance des scheduler doivent être unique dans tout le cluster. Pour
simplifier la configuration, on peut utiliser AUTO ainsi les fichiers quartz.properties sont
identiques sur chaque instance.
En fait chaque scheduler utilisant la datasource sera considéré comme faisant partie du
cluster. Les instances communiquent par l’intermédiaire de la datasource. Ainsi aucune autre
configuration n’est nécessaire (par exemple au niveau du réseau). Le paramètre
clusterCheckinInterval permet de vérifier l’état du cluster à chaque instant et observer les
instances disponibles.

org.quartz.scheduler.instanceId = AUTO

#==========================================================================

org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000

Récupération d’un job par le scheduler :


job.setRequestsRecovery(true);

3.3.5 Plugins
Si certaines fonctionnalités de Quartz manquent ou ne sont pas adaptés à l’application métier,
il est possible de les développer en tant que plugins

#==========================================================================
# Configure Plugins
#==========================================================================

org.quartz.plugin.jobHistory.class =
org.quartz.plugins.history.LoggingJobHistoryPlugin

Quartz fournis également quelques plugins utiles :

JobInitializationPlugin : Lit le paramétrage des Jobs et Trigger dans un fichier XML


LoggingJobHistoryPlugin, LoggingTriggerHistoryPlugin : Trace tous les évènements des
Jobs et Triggers en s’appuyant sur la librairie Apache-Common-Logging.
ShutdownHookPlugin Le scheduler est notifié de tout arrêt de la JVM (par CTRL-C ou par
crash) et libère ses ressources proprement.

3.4 Quelques Jobs utiles fournis avec Quartz :

SendMailJob - Envoi de Mail :


Ce type de Job permet d’envoyer un mail selon les paramètres fournis par le client (le serveur
smtp, l’émetteur, le destinataire, le sujet, le corps, …)
JobDetail jobDetail = new JobDetail("myJob",
Scheduler.DEFAULT_GROUP,
SendMailJob.class);
JobDataMap map = new JobDataMap();
map.put(SendMailJob.PROP_SMTP_HOST,"smtp.bidule.fr");
map.put(SendMailJob.PROP_SENDER,"sender@bidule.fr");
map.put(SendMailJob.PROP_RECIPIENT,"recipient@bidule.fr");
map.put(SendMailJob.PROP_SUBJECT,"hello");
map.put(SendMailJob.PROP_MESSAGE,"just say hello. Please don’t reply...");
jobDetail.setJobDataMap(map);

Librairies supplémentaires lib/javamail.jar et lib/activation.jar


requises

EJBInvokerJob - Invocation d’EJB :


Ce type de Job permet de déléguer le traitement à un EJB.
Trois paramètres sont nécessaires dans la définition du job par le client : le nom JNDI de
l’EJB, la méthode exécutée par le Job et la liste d’arguments de cette méthode.
Attention : Il n’est pas nécessaire de créer un contexte de connexion au serveur si le
scheduler est lancé dans la même JVM. Pour lancer le scheduler en même temps qu’un
serveur voir § 3.5. Si le scheduler réside dans une autre JVM, les paramètres de connexion au
serveur peuvent être placés dans le JobDataMap ou dans les propriétés d’environnement de la
JVM (System.properties). Toutefois s’il est nécessaire de fournir une authentification
(Principal et Credentials), on ne peut manifestement le faire que dans System.properties.

Exemple avec un serveur Weblogic et un scheduler séparé.


JobDetail jobDetail = new JobDetail("myJob",
Scheduler.DEFAULT_GROUP,
EJBInvokerJob.class);

JobDataMap map = new JobDataMap();


//System.setProperty(InitialContext.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.
WLInitialContextFactory");
//System.setProperty(InitialContext.PROVIDER_URL,"t3://192.168.1.2:7001");

map.put(EJBInvokerJob.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialConte
xtFactory");
map.put(EJBInvokerJob.PROVIDER_URL,"t3://192.168.1.2:7001");
map.put(EJBInvokerJob.EJB_JNDI_NAME_KEY,"application/Sales");
map.put(EJBInvokerJob.EJB_METHOD_KEY,"reportByClientId");
map.put(EJBInvokerJob.EJB_ARGS_KEY,new Object[]{"152-12568"});

Librairies supplémentaires Librairie du serveur d’application (par ex le jar client


requises weblogic)

JMXInvokerJob - Invocation de MBean.


Ce job permet d’invoquer une méthode sur un ManagementBean d’un serveur d’applications,
via l’API JMX. Les détails d’invocation sont totalement transparents.
Attention : il est nécessaire que le scheduler soit dans la même JVM que le serveur. Pour
lancer le scheduler en même temps qu’un serveur voir § 3.5 .La recherche du serveur de
MBean s’effectue au moyen de MBeanServerFactory.findMBeanServer(null) qui renvoie la
liste des serveurs présents dans la JVM. Pour une invocation à distance, il faudra créer un Job
spécifique.
FileScanJob
Vérifie si un fichier a été modifié entre deux exécutions du job. Ce type de job est
stateful car il doit conserver la date lue à l’exécution précédente.

NativeJob
Lance une commande du système d’exploitation.

NoOpJob
Ne fait rien… ou presque ! Le JobListener est quand même notifié, ce qui permet de
déporter le traitement dans ce dernier (c'est-à-dire coté client…)

3.5 Démarrer le scheduler en même temps qu’un serveur web


Selon votre choix d’architecture, vous souhaiterez peut-être que le scheduler réside sur le
même serveur que l’application cliente. Or comment s’assurer simplement que celui-ci soit
disponible dès le démarrage? Quartz fournit pour cela une Servlet à appeler au démarrage.
Pour l’utiliser il suffit d’insérer ces quelques lignes dans le fichier de configuration de votre
application web (web.xml) :
<servlet>
<servlet-name>
QuartzInitializer
</servlet-name>
<display-name>
Quartz Initializer Servlet
</display-name>
<servlet-class>
org.quartz.ee.servlet.QuartzInitializerServlet
</servlet-class>
<load-on-startup>
1
</load-on-startup>
<init-param>
<param-name>config-file</param-name>
<param-value>/un/dossier/quartz.properties</param-value>
</init-param>
<init-param>
<param-name>shutdown-on-unload</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
config-file : le fichier quartz.properties à charger.
shutdown-on-unload : lorsque le serveur est arrêté, permet également d’arrêter le scheduler
proprement.

4 Conclusion
Nous venons d’aborder deux façons différentes de planifier des tâches en Java. Le Timer du
JDK est certes moins évolué que Quartz mais à l’avantage d’être plus simple et de faire partie
de l’API Standard. Le Timer est réservé à des applications qui peuvent se passer de services
tels que la persistance et l’équilibrage de charge, qui ne se soucient pas d’être notifié des
tâches perdues lors d’un crash ou qui ne souhaitent pas de contrôle sophistiqué des
planifications et des tâches.
Il est à noter qu’IBM et BEA ont déposé une spécification d’API standard pour la
planification de tâche gérée par un serveur d’application (prise en charge des ressources par le
conteneur). JSR 236: Timer for Application Servers

5 Références
[1] Javadoc 1.4.2 de java.util.Timer :
http://java.sun.com/j2se/1.4.2/docs/api/java/util/Timer.html

[2] Apache Software Foundation (ASF) :


http://www.apache.org/licenses/

[3] Site officiel de Quartz :


http://www.opensymphony.com/quartz/

[4] Site officiel de Flux :


http://www.fluxcorp.com/

[5] Article du site OnJava sur lequel s’est basé ce tutorial :


http://www.onjava.com/pub/a/onjava/2004/03/10/quartz.html

Vous aimerez peut-être aussi