SeamFramework.orgCommunity Documentation

Chapitre 22. Asynchronisme et messagerie

22.1. Messagerie dans Seam
22.1.1. La configuration
22.1.2. L'expédition de messages
22.1.3. Réception des messages en utilisant tout bean conducteur de message
22.1.4. La réception des message dans le client
22.2. Asynchronisme
22.2.1. Le sméthodes assynchrones
22.2.2. Les méhtodes assynchrones avec le Dispatcher Quartz
22.2.3. Les évènements assynchrones
22.2.4. La gestion des exceptions pour les appels assynchrones

Seam rends vraiment très simple de réaliser du travail de manière asynchrone depuis une requête web. Quand la plus part des gens pensent asynchronisme en Java EE, ils pensent à l'utilisation de JMS. C'est certainement un bonne façon d'approche le problème avec Seam, et c'est la bonne raison quand vous avez une qualité de service stricte et bien définie dans les prérequis de service. Seam rends facile d'envoyer et de recevoir des messages JMS en utilisant les composants de Seam.

Mais pour la plus part des cas d'utilisation, JSM est excessif. Les couches de Seam une méthode symple asynchrone et une fonction évènementiel par dessus votre choix d'un dispatchers:

Ce chapitre couvre en premier lieu comment exploiter Seam pour simplifier JMS et ensuite explique comment utiliser la méthode assynchrone la plus simple et la fonction évènementielle.

Seam rends facile d'envoyer et de recevoir des messages JMS depuis et vers des composants de Seam. A la fois le publiant du message et le destinataire du message peuvent être des composants de Seam.

Vous allez en premier apprendre à configurer une file d'attente et un publiant de sujet de message et ensuite regarder les exemples qui illustre comment réaliser l'échange de messages.

Vous pouvez manipuler les messages en utilisant tout EJB3 conducteur de message. Les beans conducteur de message peuvent même être des composants de Seam et dans ce cas il est possible d'injecter d'autres évènements et d'autres composants de Seam d'étendue applicatif. Voici un exemple de la réception d'un paiement qui délègue vers un processude paiement.

En premier lieu, créez un MDB pour recevoir le message.

@MessageDriven(activationConfig = {

    @ActivationConfigProperty(
        propertyName = "destinationType",
        propertyValue = "javax.jms.Queue"
    ),
    @ActivationConfigProperty(
        propertyName = "destination",
        propertyValue = "queue/paymentQueue"
    )
})
@Name("paymentReceiver")
public class PaymentReceiver implements MessageListener
{
   @Logger private Log log;
   @In(create = true) private PaymentProcessor paymentProcessor;
    
   @Override
   public void onMessage(Message message)
   {
      try
      {
         paymentProcessor.processPayment((Payment) ((ObjectMessage) message).getObject());
      } 
      catch (JMSException ex)
      {
         log.error("Message payload did not contain a Payment object", ex);
      } 
   }
}

Ensuite, implémentz le composant de Seam vers le quel la réception de la délégation du processus de paiement.

@Name("paymentProcessor")

public class PaymentProcessor
{
   @In private EntityManager entityManager;
   public void processPayment(Payment payment)
   {
      // perhaps do something more fancy
      entityManager.persist(payment);
   }
}

Si vous essayeez de réaliser des opérations de transactions dans votre MDB, vous devriez vous assurer que vous travailler avec une source de données XA.Sinon, il ne sera pas possible d'invalider les modifications de la base de données si la validation de la transaction de la base de données et que les opérations sousjacentes qui sont réalisées par le message échouent.

Les évènements assynchrones et les appels de méthodes ont la même attente en qualité de services que le mécanisme de distribution sousjacent. Le distributeur par défaut, basé sur un ScheduledThreadPoolExecutor réalise de manière efficace mais ne fournissent pas de support pour les tâches assynchrones persistances et ne donne aucune garantie que la tâche ne sera réellement exécutée. Si vous travaillez dans un environnement qui supporte EJB 3.0 et ajoutez la ligne suivante à components.xml:


<async:timer-service-dispatcher/>

ainsi votre tâche assynchrone sera réalisée par le service de temps EJB du containeur. Si vous n'êtes pas familier avec le service Timer, ne vous inquiétez pas, vous n'allez pas voir besoin d'interragir directement si vous vouler utiliser les méthodes de manière assynchrone dans Seam. La chose importante à savoir et que tout bonne implémentation de EJB 3.0 aura l'option d'utiliser le cadenseur de persistance ce qui donne quelques garanties que la tâches sera éventuellement exécutée.

Une autre alternative est d'utiliser la bibliothèque opensource Quartz pour gérer la méthode assynchrone. Vous avez besoin de fournir le JAR de la bibliothèque Quartz (à mettre dans le dossier lib) dans votre EAR et de le déclarer dans un module Java dans application.xml. Le distributeur Quartz peut être configuré en ajoutant un fichier de propriété Quartz dans le chemin des clases. Il doit être dénomé seam.quartz.properties. De plus, vous allez avoir besoin d'ajouter la ligne suivante à components.xml pour installer le distributeur Quartz.


<async:quartz-dispatcher/>

L'API de Seam par défaut ScheduledThreadPoolExecutor, le Timer EJB3, et le Scheduler Quartzz sont largement identique. Ils peuvent simplement être "plug and play" en ajoutant une ligne à components.xml.

Dans le formulaire le plus simple, un appel assynchrone permet simplement un appel d'une méthode être assynchrone (dans un processus d'exécution déifférent) depuis l'appelant. Nous utilisons habituellement un appel assynchrone quand nous voulons retourner une réponse immédaite au client, et avoir quelques travaux couteux être réalisé en arrière plan. Ce modèle fonctionne très bien dans les applications qui utilisent AHAX? quand le client peut être automatiquement questionner le serveur pour le résultat du travail.

Pour les composants EJB, nous annotons l'interface local pour spécifier qu'une méthode est un processus assynchrone.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public void processPayment(Payment payment);
}

(Pour les composants JavaBean nous pouvons annoter la classe d'implémentation du composant si vous voulez.)

L'utilisation de l'assynchronisme est transparente à la classe du bean;

@Stateless

@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    public void processPayment(Payment payment)
    {
        //do some work!
    }
}

et aussi transparent pour le client:

@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String pay()
    {
        paymentHandler.processPayment( new Payment(bill) );
        return "success";
    }
}

La méthode assynchrone est exécuté dans un contexte d'évènement complètement nouveau et n'a pas d'accès à la session ou l'état du contexte de conversation de l'appelant. Cependant le contexte du processus métier est propagé.

Les appels de méthodes assynchrone peuvent être programmé pour une exécution décalée dans le temps en utilisant les annotaiotns @Duration, @Expiration et @IntervalDuration.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public void processScheduledPayment(Payment payment, @Expiration Date date);
    @Asynchronous
    public void processRecurringPayment(Payment payment, 
                                        @Expiration Date date, 
                                        @IntervalDuration Long interval)'
}
@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() );
        return "success";
    }
    public String scheduleRecurringPayment()
    {
        paymentHandler.processRecurringPayment( new Payment(bill), bill.getDueDate(), 
                                                ONE_MONTH );
        return "success";
    }
}

A la fois le client et le serveur peuvent avoir accès à l'objet Timer associé avec l'invocation. L'objet Timer visible ci-dessous est un timer EJB3 quand vous utilisez le dispatcher EJB3. Par défaut avec ScheduledThreadPoolExecutor, l'objet retourné est Future du JDK. Pour le dispatcher Quartz, il retourne le QuartzTriggerHandle, qui sera vu dans la prochaine section.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public Timer processScheduledPayment(Payment payment, @Expiration Date date);
}
@Stateless

@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    @In Timer timer;
    
    public Timer processScheduledPayment(Payment payment, @Expiration Date date)
    {
        //do some work!
        
        return timer; //note that return value is completely ignored
    }
}
@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        Timer timer = paymentHandler.processScheduledPayment( new Payment(bill), 
                                                              bill.getDueDate() );
        return "success";
    }
}

Les méthodes assynchrones peuvent retourner une autre valeur à l'appelant.

Le dispatcher Quartz (voir plus haut sur comment l'installer) vous permet d'utiliser les annotations @Asynchronous, @Duration, @Expiration, et @IntervalDuration vue au-dessus. Mais il y a quelques fonctionnalités additionnelles puissantes. Le dispatcher Quartz supporte les trois nouvelles annotations.

L'annotation @FinalExpiration indique une date de fin pour la tâche récurrante. Notez que vous pouvez injecter le QuartzTriggerHandle.



        @In QuartzTriggerHandle timer;
        
    // Defines the method in the "processor" component
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalDuration Long interval,
                                 @FinalExpiration Date endDate, 
                                 Payment payment) 
    { 
        // do the repeating or long running task until endDate
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    // Starts now, repeats every hour, and ends on May 10th, 2010
    Calendar cal = Calendar.getInstance ();
    cal.set (2010, Calendar.MAY, 10);
    processor.schedulePayment(new Date(), 60*60*1000, cal.getTime(), payment);

Notez que la méthode retourne un objet QuartzTriggerHandle, qui vous pouvez utiliser pour arréter, mettre en pause et reprendre le plannificateur. L'objet QuartzTriggerHandle est sérialisable, ainsi vous pouvez le sauver dans la base de données si vous avez besoin de le conserver sous le coude pour une période de temps un peu plus longue.

QuartzTriggerHandle handle =

         processor.schedulePayment(payment.getPaymentDate(), 
                                   payment.getPaymentCron(), 
                                   payment);
        payment.setQuartzTriggerHandle( handle );
        // Save payment to DB
        
        // later ...
        
        // Retrieve payment from DB
        // Cancel the remaining scheduled tasks
        payment.getQuartzTriggerHandle().cancel();

L'annotation @IntervalCron supporte a syntaxe des tâches cron d'Unix pour la planification des tâches. Par exemple, la méthode assynchrone suivante s'exécutera à 14h10 et at 14h44 chaque mercredi du mois de Mars.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalCron String cron, 
                                 Payment payment) 
    { 
        // do the repeating or long running task
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), "0 10,44 14 ? 3 WED", payment);

L'annotation @IntervalBusinessDay supporte l'invocation du scénario "n-ième jour ouvré" . Par exemple, la méthode assynchrone suivante sera exécuté à 14h00 le deuxième jour ouvré de chaque mois. Par défaut, cela exclu tous les week-end et les vacances fédérales des USA jusqu'en 2010 des jours ouvrés.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalBusinessDay NthBusinessDay nth, 
                                 Payment payment) 
    { 
        // do the repeating or long running task
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), 
          new NthBusinessDay(2, "14:00", WEEKLY), payment);

L'objet NthBusinessDay contient la configuration du déclencheur de l'invocation. Vous pouvez indiquer plus de vacances (par exemple, les vacances spécifiques à l'entreprise, des vacances autre que pour les USA, etc.) via la propriété additionalHolidays.



public class NthBusinessDay implements Serializable
{
      int n;
      String fireAtTime;
      List <Date
> additionalHolidays;
      BusinessDayIntervalType interval;
      boolean excludeWeekends;
      boolean excludeUsFederalHolidays;
      public enum BusinessDayIntervalType { WEEKLY, MONTHLY, YEARLY }
      public NthBusinessDay ()
      {
        n = 1;
        fireAtTime = "12:00";
        additionalHolidays = new ArrayList <Date
> ();
        interval = BusinessDayIntervalType.WEEKLY;
        excludeWeekends = true;
        excludeUsFederalHolidays = true;
      }     
      ... ...
}

Les annotations @IntervalDuration, @IntervalCron, et @IntervalNthBusinessDay sont mutuellement exclusives. Si elles sont utilisées sur la même méthode, une RuntimeException sera déclenchée.

Chaque dispatcher assynchrone fonctionne différemment quand une exception est propagé au travers. Par exemple, le dispatcher java.util.concurrent suspendra de future exécutions d'un appel qui se répète, et le service timer EJB3 avalera l'exception. Seam cependant capture toute les exceptions qui seront propagées à l'extérieur de l'appel assynchrone avant qu'elle n'atteigne le dispatcher.

Par défaut, tout exception qui sera propagée à l'extérieur de l'exécution assynchrone sera intercepté et enregistré dans le journal au niveau erreur. Vous pouvez personnaliser cette fonctionnalité globallement en surchargeant le composant org.jboss.seam.async.asynchronousExceptionHandler:

@Scope(ScopeType.STATELESS)

@Name("org.jboss.seam.async.asynchronousExceptionHandler")
public class MyAsynchronousExceptionHandler extends AsynchronousExceptionHandler { 
   @Logger Log log;
   
   @In Future timer;
   
   @Override
   public void handleException(Exception exception) {
      log.debug(exception);
      timer.cancel(false);
   }
   
}

Voici, par exemple, en utilisant le dispatcher java.util.concurrent, nous l'injectons l'objet de controle et annulons toute invocation future quand une exception est rencontrée

Vous pouvez aussi altérer cette fonctionnalité pour un seul composant en implémentant la méthode public void handleAsynchronousException(Exception exception); du composant. Par exemple:

   public void handleAsynchronousException(Exception exception) {

      log.fatal(exception);
   }