SeamFramework.orgCommunity Documentation
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:
java.util.concurrent.ScheduledThreadPoolExecutor
(par défaut)
le service de temps EJB (pour les environements EJB 3.0)
Quartz
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.
Pour configurer l'infrastructure de Seam pour l'expédition de messages JMS, vous avez besoin d'indiquer à Seam à propos de quel sujets et de quelle file d'attente vous voulez que les messages soient envoyés et aussi dire à Seam où il peut trouvrer le QueueConnectionFactory
et/ou le TopicConnectionFactory
.
Seam utilisant par défaut Seam UIL2ConnectionFactory
qui est une fabrique de connection usuelle à utiliser avec JBossMQ. Si vous voulez utiliser un autre fournisseur de JMS, vous avez besoin de définir un ou deux queueConnection.queueConnectionFactoryJndiName
et topicConnection.topicConnectionFactoryJndiName
dans seam.properties
, web.xml
ou components.xml
.
ous avez aussi besoin de lister les sujets et les files d'attentes dans components.xml
pour installer TopicPublisher
s et QueueSender
gérés par Seam:
<jms:managed-topic-publisher name="stockTickerPublisher"
auto-create="true"
topic-jndi-name="topic/stockTickerTopic"/>
<jms:managed-queue-sender name="paymentQueueSender"
auto-create="true"
queue-jndi-name="queue/paymentQueue"/>
Maintenant, vous pouvez injecter un TopicPublisher
et un TopicSession
de JMS dans chaque composant de Seam pour publier un objet dans un sujet:
@Name("stockPriceChangeNotifier")
public class StockPriceChangeNotifier
{
@In private TopicPublisher stockTickerPublisher;
@In private TopicSession topicSession;
public void publish(StockPrice price)
{
try
{
stockTickerPublisher.publish(topicSession.createObjectMessage(price));
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
}
Ou, dans une file d'attente:
@Name("paymentDispatcher")
public class PaymentDispatcher
{
@In private QueueSender paymentQueueSender;
@In private QueueSession queueSession;
public void publish(Payment payment)
{
try
{
paymentQueueSender.send(queueSession.createObjectMessage(payment));
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
}
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.
Vous allez avoir besoin de créer un attribut avec l'annotation @In
à vrai (i.e. create = true) pour faire que Seam créer une instance du composant injecté. Ce n'est pas nécéssaire si le composant dispose de l'auto-création (autrement dit, s'il est annoté avec @Autocreate
).
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.
Le Seam Remoting vous permet de vous abonner à un sujet JMS depuis le JavaScript côté client. Ceci est décrit dans Chapitre 25, Remoting.
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.
Les évènements conducteurs de composants peuvent aussi être assynchrone. Pour déclencher un évènement pour une exécution assynchrone, un simple appel à la méthode raiseAsynchronousEvent()
de la classe Events
. Pour programmer un évènement dans le temps, l'appel à la méthode raiseTimedEvent()
, en passant un objet schedule (pour le dispatcher par défaut ou pour le dispatcher de service de temps, utilisez TimerSchedule
). Les composants peuvent observer des évènements assynchrones de la façon usuelle, mais souvenez vous que seule le contexte du processus métier est propagé dans le processus assynchrone.
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);
}