Programmer des opérations avec Java SE et Java EE

Que ce soit pour des opérations hebdomadaires ou pour des traitements qui s’exécutent toutes les 10 secondes, les plateformes Java SE et Java EE offrent des mécanismes de programmation qui permettent de planifier des opérations facilement et avec un effort réduit. Ces mécanismes s’appuyent sur les capacités de multi-threading des plateformes et sur les capacités de gestion concurrente. Dans cet article, vous trouverez 2 exemples simples mis en oeuvre avec les EJB Timer Services sur la plateforme Java EE (1.4+) et ScheduledThreadPoolExecutor de Java SE (1.5+).

Chaque choix ayant ses limites, mon propos n’est pas de remettre en cause l’utilité d’un projet comme Quartz qui va beaucoup plus loin, certes que les EJB Timer Services, ou d’une classe simple comme Timer.

EJB Timer Services

En soi, créer un EJB Timer Service est d’une simplicité déconcertante. Evidemment il vous faudra sans doute effectuer un véritable traitement et vous intégrer avec une file d’attente JMS, comme dans mon article « Publier un message JMS dans Glassfish v3 avec appclient et ANT » pour que ce soit véritablement utile. Voici une sorte de Helloworld du type déployé sur Glassfish en Java EE 6. Comme on peut le voir n’importe qui, qui sait se servir d’un cron sait développer un EJB Timer Service; tout réside dans l’annotation Schedule :

package arkzoyd.demo;

import javax.ejb.Schedule;
import javax.ejb.Stateless;
import javax.ejb.Timer;

@Stateless
public class DemoEJBTimer {
@Schedule(second="*/10", minute="*", hour="*",
dayOfWeek="*", dayOfMonth="*", month="*",
year="*", info="DemoTimer")
private void scheduledTimeout(final Timer t) {
System.out.println("@Ping!!!");
}
}

Programmer avec ScheduledThreadPoolExecutor

Pour ce qui est de Java SE, la difficulté n’est guère plus grande, même si le niveau d’abstraction est bien moindre ; Il faut une classe qui implémente l’interface Runnable. La gestion de l’interface Callable qui permet un code retour ne permet pas de gérer des tâches répétitives, ce qui va un peu de soi en y réfléchissant mais pose un petit problème dans la mesure où, c’est bien beau de programmer des opérations mais on aime bien, en général, avoir un feedback.

J’ai donc agrémenté le Helloworld du type d’une file d’attente interne de type ArrayBlockingQueue pour permettre aux Threads exécutés de renvoyer des messages au Thread principal :

package arkoyd.demo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadExecutor {

ScheduledThreadPoolExecutor x;
ArrayBlockingQueue q;

public DemoThreadExecutor() {
x = new ScheduledThreadPoolExecutor(5);
q = new ArrayBlockingQueue(5);
x.scheduleAtFixedRate(new Job(q), 0, 1, TimeUnit.SECONDS);
x.scheduleAtFixedRate(new Job(q), 0, 5, TimeUnit.SECONDS);

while (true) {
try {
System.out.println(q.poll(60, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
DemoThreadExecutor demoThreadExecutor =
new DemoThreadExecutor();
}

class Job implements Runnable {
ArrayBlockingQueue q;

public Job(ArrayBlockingQueue q) {
this.q = q;
}

public void run() {
try {
q.put("@Ping!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

Le diable étant toujours dans le détail, je vous laisse le soin de bien réflechir dans un cas réel à tout ce qui peut se passer pour gérer les exceptions ou les verrous avec soin.