Se EJB 3 è stata una rivoluzione, si può dire che EJB 3.1 è una ben accetta evoluzione. Anche se ancora non abbiamo mai affrontato l’argomento in modo analitico, in avevamo già sottolineato che la nuova specifica EJB 3.1 tende ad una ulteriore semplificazione della piattaforma enterprise, introducendo miglioramenti che vengono incontro alla già difficile vita di noi sviluppatori…
Tra le varie cose, anche i Timer Services hanno subito qualche miglioramento atteso rispetto alla versione 3.0, di cui avevamo già avuto modo di imparare in un . Vediamo di che si tratta.
I limiti del Super Saiyan 3
Nel post precedente non ci siamo fermati a puntualizzare alcuni difetti che emergono dall’uso dei servizi di timer, ma visto che la nuova specifica li ha risolti vale la pena capire quali siano questi miglioramenti. A prima vista, i due limiti della specifica 3 erano:
- configurazione del timer solo tramite date o offset in millisecondi: se vogliamo per esempio che un certo evento parta ogni Lunedì, è necessario effettuare una serie di calcoli espressi in millisecondi o in date per capire quando farlo scadere la prima volta e le volte successive;
- inizializzazione programmatica: l’unico modo per inizializzare un timer è chiamare esplicitamente uno dei metodi dell’interfaccia
TimerService
. Come fare quindi ad avviare un timer all’avvio dell’applicazione senza richiedere l’interazione dell’utente? Una soluzione, se non l’unica, potrebbe essere quella di usare unServletContextListener
, anche se non è molto elegante tirare su un modulo web solo per avviare dei servizi del modulo EJB…
Vediamo come queste limitazioni sono state risolte con la specifica 3.1.
Pianificazione di un timer basata su calendario
Oltre ai metodi di creazione programmatica di un timer basata su date o millisecondi della specifica 3.0, la nuova specifica 3.1 introduce la possibilità di pianificare un timer basandosi sul calendario, con una sintassi ben nota agli utilizzatori dello scheduler cron di UNIX. L’API dei Timer Services si arricchisce così di 2 oggetti:
- ScheduleExpression
-
è la classe principale, è più complessa, responsabile della configurazione del timer basata sul calendario. Gli attributi e i valori che essi possono assumere sono riassunti nella seguente tabella:
Attributo Descrizione Valore di Default Valori Ammessi second
Uno o più secondi all’interno di un minuto
0
0 fino a 59. Per esempio: second="30".
minute
Uno o più minuti all’interno dell’ora
0
0 fino a 59. Per esempio: minute="15".
hour
Uno o più ore all’interno del giorno
0
0 fino a 23. Per esempio: hour="13".
dayOfWeek
Uno o più giorni della settimana
*
0 fino a 7 (sia 0 che 7 possono riferirsi a Domenica). Per esempio: dayOfWeek="3".
Sun, Mon, Tue, Wed, Thu, Fri, Sat. Per esempio: dayOfWeek="Mon".
dayOfMonth
Uno o più giorni del mese
*
1 fino a 31.
Per esempio: dayOfMonth="15".–7 fino a –1 (un numero negativo significa l’i-esimo giorno o giorni prima la fine del mese).
Per esempio: dayOfMonth="–3".Last. Per esempio: dayOfMonth="Last".
[1st, 2nd, 3rd, 4th, 5th, Last] [Sun,
Mon, Tue, Wed, Thu, Fri, Sat].
Per esempio: dayOfMonth="2nd Fri".month
Uno o più mesi dell’anno
*
1 fino a 12.
Per esempio: month="7".Jan, Feb, Mar, Apr, May,
Jun, Jul, Aug, Sep, Oct, Nov, Dec. Per esempio: month="July".year
Un anno particolare
*
L’anno è formato da 4 caratteri. Per esempio: year="2011".
timezone
Localizzazione del calendario
Valore di default della JVM
start
E’ possible indicate la data di inizio
Popolato al momento della creazione del timer
end
Data cancellazione timer
Gli attributi possono essere popolati con:
-
Valori singoli:
second = "10", month = "Sep";
-
Wildcard: sono tutti i valori possibili per un attributo.
second = "*", month = "*";
-
Range: intervallo inclusivo dei valori ammessi per un certo attributo, separati da un trattino (-).
second = "1-10", dayOfWeek = "Fri-Mon", dayOfMonth = "27-3" (equivalente a "27-Last , 1-3");
-
Liste: serie di valori singoli o range separati da virgola.
second = "10, 20, 30", month = "Jan-Jul, Sep-Dec";
-
Incrementi: con una barra (/) si separa un momento iniziale da un intervallo. Nel caso di x/y, si indica il valore y-esimo ammesso per un certo attributo a partire dal momento x.
minute = "*/5", hour="*" (ogni 5 minuti per ogni ora), second = "30/10" (ogni 10 secondi a partire dal 30°, equivalente a 30,40,50).
-
Valori singoli:
- TimerConfig
-
Estende la configurazione dei timer a singola azione e a intervalli durante la loro creazione. In esso è possibile specificare:
- l’oggetto info (serializzabile) da passare al metodo di callback al timeout;
- indicare se il timer deve essere persistito o meno. Ciò significa che nel caso in cui il server non sia attivo al momento del timeout, il task schedulato verrà eseguito non appena il server sarà nuovamente attivo.
Questi oggetti vengono passati come parametri ai nuovi metodi dell’interfaccia TimerService:
Timer createCalendarTimer(ScheduleExpression schedule);
Timer createCalendarTimer(ScheduleExpression schedule, TimerConfig timerConfig);
Timer createIntervalTimer(Date initialExpiration, long intervalDuration, TimerConfig timerConfig);
Timer createIntervalTimer(long initialDuration, long intervalDuration, TimerConfig timerConfig);
Timer createSingleActionTimer(Date expiration, TimerConfig timerConfig);
Timer createSingleActionTimer(long duration, TimerConfig timerConfig);
I nomi dei metodi sono abbastanza eloquenti e non richiedono grosse spiegazioni…
Per non perdere il vizio, giusto due righe di codice…
Vediamo adesso con un esempio banale come schedulare due timer: uno basato su calendario e l’altro tramite TimeConfig
:
@Stateless
public class EJB31TaskScheduler implements EJB31TaskSchedulerLocal {
private static final Logger logger = Logger
.getLogger(EJB31TaskScheduler.class);
@Resource
private TimerService timerService;
@Override
public void scheduleCalendarTimer() {
ScheduleExpression scheduleExpression = new ScheduleExpression();
scheduleExpression.second("*/5").minute("1-59").hour("*");
this.timerService.createCalendarTimer(scheduleExpression);
logger.info("Scheduling task every 5 seconds in any minutes and hours");
}
@Override
public void scheduleSingleActionTimerWithConfig() {
TimerConfig config = new TimerConfig();
config.setInfo("Executing single action timer");
config.setPersistent(false);
this.timerService.createSingleActionTimer(2000, config);
logger.info("Scheduling single action timer");
}
@Timeout
public void doTask(Timer timer) {
if (timer.getInfo() != null)
logger.info(timer.getInfo());
else
logger.info("Calendar-based task executed");
}
@Override
public void cancelAllTimers() {
Collection timers = this.timerService.getTimers();
logger.info("Cancelling " + timers.size() + " timer/s");
for (Timer timer : timers) {
timer.cancel();
}
}
}
- Il metodo
scheduleCalendarTimer()
istanzia un timer usando in modo misto le notazioni disponibili: crea un timer ogni 5 secondi per ogni minuto (ovvero "1-59") di ogni ora (indicata con "*"). La vera differenza con i timer visti fino adesso è che il timer non scadrà ogni 5 secondi da quando viene lanciato, ma esattamente il 5°, 10°, 15°, 20°, ecc… secondo di ogni minuto per ogni ora. Fatto questo, vi comunico che abbiamo appena creato un timer infinito!! Come mai? Perché gli attributi oltre le ore hanno default "*"!! Per interromperlo basta quindi chiamare il metodocancelAllTimers()
, altrimenti vi troverete il timer attivo anche dopo il riavvio del server (ricordate che sono persistenti di default?). - Come si capisce dal nome, il metodo
scheduleSingleActionTimerWithConfig()
istanzia un timer one-shot tramite l’oggettoTimerConfig
, passando un parametro alla funzione di callbackdoTask(Timer timer)
e specificando che non dovrà essere persistito. Da notare che quest’ultimo metodo non ha l’annotazione@Override
: a differenza della specifica 3, il metodo di timeout può non essere dichiarato nell’interfaccia di business (può anche essere non pubblico). Questo metodo infatti non dovrebbe essere mai chiamato direttamente!
Configurazione automatica
La grande novità della nuova specifica 3.1 riguarda la possibilità di configurare in modo dichiarativo un timer basato su calendario tramite semplici annotazioni:
- al momento del deploy, se un metodo di un EJB viene annotato con
@Schedule
o@Schedules
, viene configurato in modo automatico. Non c’è più bisogno quindi di inizializzazioni tramiteServletContextListener
per esempio. - a differenza dei timer inizializzati in modo programmatico, ogni metodo annotato è automaticamente un metodo di callback chiamato allo scadere del timer: posso quindi avere più callback all’interno dello stesso EJB.
@Schedule
si configura e si comporta come un timer istanziato con ScheduleExpression
: oltre ai suoi attributi ha anche persistent e info che già conosciamo da TimerConfig
.
E’ possibile quindi pianificare per esempio un batch notturno che parta alle 23 prima del week-end in questo modo:
@Schedule(dayOfWeek="Fri", hour="23")
public void nightlyVampire() {
logger.info("We suck young blood");
}
Conclusioni
Il sistema di schedulazine offerto da Java EE 6 è notevolmente migliorato rispetto ai suoi predecessori grazie alle due caratteristiche descritte. La pianificazione basata su calendario evita di calcolare a mano le date e gli intervalli come era necessario fare fino ad oggi e la possibilità di configurare un timer anche in modo automatico rende la loro inizializzazione sempre più immediata.