Batch java – tâches réentrantes avec Spring 3




 

 
Dans un précédent article, nous avions abordé la programmation de tâches avec Spring 3. Ici, nous allons aller un peu plus loin en permettant l’exécution d’une tâche programmée dans plusieurs processus simultanés, toujours en développement java.

 

Pré-requis

Java installé
Eclipse IDE
MAVEN 2 installé (voir Installer MAVEN 2 sous Linux ou Installer MAVEN 2 sous Windows)
Spring 3 framework
– Namespace task de Spring 3, lire Ordonnancement de tâches avec Spring 3

 

Réentrant, c’est quoi ?

On dit qu’un programme est réentrant s’il peut être exécuté par plusieurs processus simultanément.

 

Réentrant, exemple

Prenons une tâche T qui dure en moyenne 30s, et qui est programmée pour s’exécuter toutes les 10s. Si la tâche T est réentrante, toutes les 10s une nouvelle instance de T s’exécutera :
T1 démarre à 0s
T2 démarre à 10s, T1 continue de s’exécuter
T3 démarre à 20s, T1 et T2 continuent de s’exécuter
T4 démarre à 30s, T2 et T3 continuent de s’exécuter, T1 s’arrête (échéance de la durée de traitement de 30s)
…….

 

Notre projet exemple

Pour la création du projet MAVEN 2 sous Eclipse, je vous laisse vous référer à l’article Utilisation des archétypes MAVEN 2 en choisissant comme archetype maven-archetype-quickstart, pour groupId kezen.scheduler.exemple, et pour artifactId kezen-scheduler. Pour le reste, faites le choix par défaut.
Ensuite, rajoutez deux packages : kezen.scheduler.exemple.nonreentrant et kezen.scheduler.exemple.reentrant, ceci afin que nos deux exemples s’exécutent indépendemment l’un de l’autre.

 

Les dépendances

Ajouter les dépendances suivantes au fichier pom.xml de votre projet :

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aop</artifactId>
 <version>3.0.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>3.0.0.RELEASE</version>
</dependency>

Mettez à jour vos dépendances via la commande : mvn clean eclipse:clean eclipse:eclipse

 

Le fichier Spring applicationContextNonReentrant.xml

Créez le dossier resources sous src/main de votre projet. Créez le fichier applicationContextNonReentrant.xml dans ce dossier, et ajoutez le code suivant :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:task="http://www.springframework.org/schema/task"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                      http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"&gt;
  <context:component-scan base-package="kezen/scheduler/exemple/nonreentrant"/>
    <task:annotation-driven/>
</beans>

Pour rappel:
context:component-scan va chercher les annotations présentes dans le code dans le package kezen/scheduler/exemple/nonreentrant;
task:annotation-driven indique que le scheduling est paramétré via les annotations.

 

Le code

Nous allons commencé par créer une simple tâche schédulée, qui s’éxécutera pendant 10s, et qui sera lancé toutes les 5s. Créez dans le package kezen.scheduler.exemple.nonreentrant, la classe suivante :

package kezen.scheduler.exemple.nonreentrant;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ScheduledProcessorWithoutWorker 
{
  @Scheduled(fixedDelay = 5000)
  public void process() 
  {
    String threadName = Thread.currentThread().getName(); 
    System.out.println("   " + threadName + " beginning work");
    try 
    {
      Thread.sleep(10000);
    } catch (InterruptedException e) 
    {
      e.printStackTrace();
    }
    System.out.println("   " + threadName + " completed work");
  }
}

Pour rappel, c’est l’annotation @Scheduled placée avant la méthode qui indique qu’elle sera appelée selon les critères donnés en paramètre. Ces trois critères exclusifs sont trois formes différentes de configurer l’ordonnancement : cron, fixedDelay, et fixedRate. N’oubliez pas l’annotation @Service, sans quoi le job ne démarrera pas. Le Job créé ici est non REENTRANT, une seule instance à la fois sera exécutée.

 

Exécution du code

Nous créons une classe d’exécution (avec la méthode void main), qui va charger le fichier applicationContextNonReentrant.xml, créé ci-dessus :

package kezen.scheduler.exemple;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SchedulerNonReentrant 
{
  public static void main(String[] args) 
  {
    new ClassPathXmlApplicationContext("applicationContextNonReentrant.xml");
  }
}

Il n’y a plus qu’à l’éxécuter. Vous constaterez qu’un job à la fois s’éxécute, le code est non réentrant.

 

Le code REENTRANT

Pour rendre notre code réentrant, nous allons devoir créer une nouvelle classe qui sera appelée par notre Job. Cette nouvelle classe exécutera le code métier à proprement parler et c’est Spring qui injectera (via l’annotation @Autowired) cette classe dans notre Job. Du fait de Spring, cette classe doit implémenter une interface. Je vous l’avoue; ce choix imposé par Spring reste discutable, dans le cas présent, mais rend des services dans d’autres.

Le code de l’interface :

package kezen.scheduler.exemple.reentrant;

public interface ISchedulerWorker 
{
  public void work();
}

Et nous créons donc la classe SchedulerWorker implémentant l’interface ISchedulerWorker comme ci-dessous :

package kezen.scheduler.exemple.reentrant;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class SchedulerWorker implements ISchedulerWorker 
{
  @Async
  public void work() 
  {
    String threadName = Thread.currentThread().getName(); 
    System.out.println("   " + threadName + " beginning work");
    try 
    {
      Thread.sleep(10000);
    }
    catch (InterruptedException e) 
    {
      Thread.currentThread().interrupt();
    }
    System.out.println("   " + threadName + " completed work");
  }
}

Vous aurez remarqué que cette classe est identique à notre classe ScheduledProcessorWithoutWorker, seules les annotations changent. L’annotation la plus importante est @Async qui indique que le code est exécuté de manière asynchrone : une fois démarré, il rend la main au processus lanceur.

 

Le scheduler REENTRANT

Le scheduler prend en propriété le SchedulerWorker créé précédemment, dont il lancera la méthode work à intervalle régulier. Créer dans le package kezen.scheduler.exemple.reentrant, la classe suivante :

package kezen.scheduler.exemple.reentrant;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ScheduledProcessorWithWorker 
{
  @Autowired
  private ISchedulerWorker schedulerWorker;
  @Scheduled(fixedDelay = 5000)
  public void process() 
  {
    schedulerWorker.work();
  }
}

 

Le fichier Spring applicationContextReentrant.xml

Créez le fichier applicationContextReentrant.xml dans le dossier src/main/resources, et ajoutez le code suivant :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="kezen/scheduler/exemple/reentrant"/>
    <task:annotation-driven/>
</beans>

Vous remarquerez qu’il est quasi identique au de l’applicationContextNonReentrant.xml, la seule différence est la base-package.

 

Exécution du code REENTRANT

Nous créons une classe d’exécution (avec la méthode void main), qui va charger le fichier applicationContextReentrant.xml, créé ci-dessus. Créez dans le package kezen.scheduler.exemple.reentrant, la classe suivante :

package kezen.scheduler.exemple.reentrant;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SchedulerReentrant 
{
  public static void main(String[] args) 
  {
    new ClassPathXmlApplicationContext("applicationContextReentrant.xml");
  }
}

 

Conclusion

Une fois de plus, Spring 3 et les annotations nous simplifient la vie, en évitant le côté verbeux de la configuration. Bien à vous, et surtout n’hésitez pas à laisser un commentaire.

 

Posté dans javaTaggé developpement java, java, java batch, maven, Spring 3, spring scheduler, spring task, spring task reentrant  |  Laisser un commentaire

Répondre

*