SeamFramework.orgCommunity Documentation

Capitolo 22. Asincronicità e messaggistica

22.1. Asincronicità
22.1.1. Metodi asincroni
22.1.2. Metodi asincroni con il Quartz Dispatcher
22.1.3. Eventi asincroni
22.1.4. Gestione delle eccezione da chiamate asincrone
22.2. Messaggistica in Seam
22.2.1. Configurazione
22.2.2. Spedire messaggi
22.2.3. Ricezione dei messaggi usando un bean message-driven
22.2.4. Ricezione dei messaggi nel client

Seam semplifica molto l'esecuzione di lavori asincroni da una richiesta web. Quando la maggior parte delle persone pensa all'asincronicità in Java EE, pensa all'uso di JMS. Questo è certamente un modo per approcciare il problema in Seam, ed è quello giusto quando si hanno dei requisiti di qualità di servizio molto stringenti e ben definiti. Seam semplifica l'invio e la ricezione di messaggi JMS usando i componenti Seam.

Ma per molti casi d'uso, JMS è eccessivo. Seam aggiunge come nuovo layer un semplice metodo asincrono ed un meccanismo d'eventi nella scelta del dispatcher:

Eventi asincroni e chiamate a metodi hanno le stesse aspettative di qualità di servizio del meccanismo sottostante di dispatcher. Il dispatcher di default, basato su ScheduledThreadPoolExecutor opera efficientemente ma non fornisce alcun supporto ai task asincroni di persistenza, e quindi non garantisce che un task verrà effettivamente eseguito. Se si lavora in un ambiente che supporta EJB 3.0 e si aggiunge la seguente linea a components.xml:


<async:timer-service-dispatcher/>

allora i task asincroni verranno processati dal servizio EJB timer del container. Se non si è familiari come il servizio Timer, nessuna paura, non si deve interagire direttamente con esso per usare i metodi asincroni in Seam. La cosa importante da sapere è che qualsiasi buona implementazione di EJB 3.0 avrà l'opzione di usare i timer di persistenza, che danno garanzia che i task verranno processati.

Un'altra alternativa è quella di usare la libreria open source Quartz per gestire il metodo asincrono. Occorre incorporare il JAR della libreria Quartz (che si trova nella directory lib) nell'EAR e dichiararla come modulo Java in application.xml. Il dispatcher Quartz può essere configurato aggiungendo un file di proprietà Quartz al classpath. Deve essere nominato seam.quartz.properties. Inoltre occorre aggiungere la seguente linea a components.xml per installare il dispatcher Quartz.


<async:quartz-dispatcher/>

L'API di Seam per il ScheduledThreadPoolExecutor di default, il Timer EJB3, e lo Scheduler Quartz sono più o meno la stessa cosa. Vengono azionati come "plug and play" aggiungendo una linea a components.xml.

Nella forma più semplice, una chiamata asincrona consente che una chiamata di metodo venga processata asincronicamente (in un thread differente) dal chiamante. Solitamente si usa una chiamata asincrona quando si vuole ritornare una risposta immediata al client, e si lascia in background il lavoro dispendioso da processare. Questo pattern funziona molto bene nelle applicazioni che usano AJAX, dove il client può automaticamente interrogare il server per ottenere il risultato del lavoro.

Per i componenti EJB si annota l'interfaccia locale per specificare che un metodo viene processato asincronicamente.

@Local

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

(Per i componenti JavaBean, se si vuole, si può annotare la classe d'implementazione del componente.)

L'uso dell'asincronicità è trasparente alla classe bean:

@Stateless

@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    public void processPayment(Payment payment)
    {
        //fai qualche lavoro!
    }
}

E è anche trasparente al 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";
    }
}

Il metodo asincrono viene processato in un contesto eventi completamente nuovo e non ha accesso allo stato del contesto sessione o conversazione del chiamante. Comunque, il contesto di processo di business viene propagato.

Le chiamate del metodo asincrono possono essere schedulate per un'esecuzione successiva usando le annotazioni @Duration, @Expiration e @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";
    }
}

Entrambi il server ed il client possono accedere all'oggetto Timer associato all'invocazione. L'oggetto Timer mostrato sotto è il timer EJB3 quando viene usato il dispatcher EJB3. Per il ScheduledThreadPoolExecutor di default, l'oggetto restituito è Future di JDK. Per il dispatcher Quartz, viene ritornato QuartzTriggerHandle, che verrà discusso nella prossima sessione.

@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)
    {
        //fai qualche lavoro!
        
        return timer; //notare che il valore di ritorno viene completamente ignorato
    }
}
@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";
    }
}

I metodi asincroni non possono ritornare al chiamante alcun altro valore.

Il dispatcher Quartz (vedere indietro come viene installato) consente di usare le annotazioni @Asynchronous, @Duration, @Expiration, e @IntervalDuration come sopra. Ma possiede anche altre potenti caratteristiche. Il dispatcher Quartz supporta tre nuove annotazioni.

L'annotazione @FinalExpiration specifica una data finale per il task ricorrente. Si noti che si può iniettare il QuartzTriggerHandle.



        @In QuartzTriggerHandle timer;
        
    // Definisce il metodo nel componente "processor"
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalDuration Long interval,
                                 @FinalExpiration Date endDate, 
                                 Payment payment) 
    { 
        // esegui il task ripetitivo o long running fino a endDate
    }
    
    ... ...
    
    // Schedula il task nel codice che processa la business logic
    // Inizia adesso, ripete ogni ora, e finisce il 10 Maggio 2010
    Calendar cal = Calendar.getInstance ();
    cal.set (2010, Calendar.MAY, 10);
    processor.schedulePayment(new Date(), 60*60*1000, cal.getTime(), payment);

Si noti che il metodo restituisce l'oggetto QuartzTriggerHandle, che si può usare per arrestare, mettere in pausa e ripristinare lo scheduler. L'oggetto QuartzTriggerHandle è serializzabile, e quindi può essere salvato nel database se deve essere presente per un periodo di tempo esteso.

QuartzTriggerHandle handle =

         processor.schedulePayment(payment.getPaymentDate(), 
                                   payment.getPaymentCron(), 
                                   payment);
        payment.setQuartzTriggerHandle( handle );
        // Salva il pagamento nel DB
        
        // pi� tardi ...
        
        // Recupera il pagamento dal DB
        // Cancella i rimanenti task schedulati
        payment.getQuartzTriggerHandle().cancel();

L'annotazione @IntervalCron supporta la sintassi Unix del cron job per la schedulazione dei task. Per esempio, il seguente metodo asincrono gira alle 2:10pm e alle 2:44pm ogni Mercoledì del mese di Marzo.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalCron String cron, 
                                 Payment payment) 
    { 
        // esegui il task ripetitivo o long running
    }
    
    ... ...
    
    // Schedula il task nel codice che processa la business logic
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), "0 10,44 14 ? 3 WED", payment);

L'annotazione @IntervalBusinessDay supporta l'invocazione sullo scenario di "Giorno Lavorativo ennesimo". Per esempio il seguente metodo asincrono gira alle ore 14.00 del secondo giorno lavorativo di ogni mese. Di default esclude tutti i weekend e le festività federali americane fino al 2010 dai giorni lavorativi.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalBusinessDay NthBusinessDay nth, 
                                 Payment payment) 
    { 
        // esegui il task ripetitivo o long running
    }
    
    ... ...
    
    // Schedula il task nel codice che processa la business logic
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), 
          new NthBusinessDay(2, "14:00", WEEKLY), payment);

L'oggetto NthBusinessDay contiene la configurazione del trigger d'invocazione. Si possono specificare più vacanze (esempio, vacanze aziendali, festività non-US, ecc.) attraverso la proprietà 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;
      }     
      ... ...
}

Le annotazioni @IntervalDuration, @IntervalCron, e @IntervalNthBusinessDay sono mutualmente esclusive. Se usate nello stesso metodo, verrà lanciata una RuntimeException.

Ogni dispatcher asincrono si comporta in modo differente quando attraverso di esso viene propagata un'eccezione. Per esempio, il dispatcher java.util.concurrent sospenderà ogni altra esecuzione di una chiamata che si ripete, ed il servizio timer EJB3 inghiottirà quest'eccezione. Quindi Seam cattura ogni eccezione che si propaga fuori da una chiamata asincrona prima che questa raggiunga il dispatcher.

Di default ogni eccezione che si propaga fuori da un'esecuzione asincrona verrà catturata e loggata come errore. Si può personalizzare questo comportamento facendo override del componente 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);
   }
   
}

Per esempio, usando il dispatcher java.util.concurrent, viene iniettato il suo oggetto di controllo e vengono cancellate tutte le future invocazioni quando si incontra un'eccezione.

Si può alterare questo comportamento per un componente individuale, implementando sul componente il metodo public void handleAsynchronousException(Exception exception);. Per esempio:

   public void handleAsynchronousException(Exception exception) {

      log.fatal(exception);
   }

Seam facilita l'invio e la ricezione di messaggi JMS da e verso componenti Seam.