Per chi è cresciuto a pane e GoF non sarà certo la prima volta che sente parlare del Pattern Decorator. In due parole, questo pattern permette di aggiungere in modo modulare funzionalità, a compile-time, ai metodi di una classe esistente, mantenendone l’interfaccia intatta. L’aggiunta o la rimozione di queste funzionalità risulta quindi totalmente trasparente all’utilizzatore. Nel contesto attuale dello sviluppo Java, sempre più governato da “container” che risolvono automaticamente le dipendenze, implementare questo pattern poteva non essere banale fino a ieri. La nuova specifica Context and Dependancy Injection (CDI) introdotta in Java EE 6 invece supporta nativamente i decoratori: vediamo come.
Interceptor vs Decorators
A prima vista uno potrebbe chiedersi: “ma che differenza c’è dalla normale AOP implementata con Interceptors“? Il concetto di interceptor lo ritroviamo sia in Spring sia nella specifica EJB che nella stessa CDI. A che serve? In pratica è uno strumento potente che permette di catturare e separare aspetti ortogonali alla logica applicativa. E’ perfetto quindi per risolvere questioni tecniche come la gestione delle transazioni, dei log o delle sicurezze. Per natura quindi gli intercettori (passate l’orrendo italianismo) non sono a conoscenza del contesto che intercettano: semplicemente permettono di eseguire operazioni prima e/o dopo la chiamata ad un metodo di business, senza entrare in merito alla logica del metodo chiamato.
E se invece avessimo bisogno di entrare in merito al contesto chiamato? Ecco, i decoratori servono proprio a questo! In CDI, un decoratore intercetta le chiamate relative a specifiche interfacce Java della nostra applicazione, entrando quindi in merito alla logica di business: possiamo pensarli come wrapper del nostro strato applicativo nel quale possiamo modellare e delegare specifiche funzionalità. In sostanza sono complementari agli interceptors, dai quali si differenziano per specificità d’impiego e conoscenza semantica del contesto in cui agiscono.
Ovviamente è possibile applicare contemporaneamente alla stessa classe sia un interceptor che un decorator. In che ordine verranno chiamati? Come ci si può attendere, l’interceptor avrà la precedenza sul decoratore. Cose più strane come intercettare un decoratore invece non si può fare… anche per fortuna direi! 😉
Un esempio di decoratore con CDI
Immaginiamo di aver inventato l’applicazione del momento che tutti stanno scaricando, e di aver la necessità di mostrare il contatore dei download che si aggiorna una volta al giorno. Ci sarà quindi una classe delegata a gestire i download che, tra l’altro, esporrà anche il metodo per il calcolo del numero di download:
public interface DownloadManager extends Serializable { ... Long getCount(); ... }
Come evitare di far eseguire i conti ogni volta che viene chiamato il metodo getCount()
senza impattare il codice già sviluppato? Un decoratore è la soluzione ideale. La teoria ci dice che deve implementare anch’esso la stessa interfaccia della classe che si vuole decorare, incapsulando una precisa responsabilità: quella cioè di “cachare” il risultato del metodo per un giorno. Vediamo subito il codice:
@Decorator public abstract class DownloadManagerCache implements DownloadManager { private static final Logger logger = Logger.getLogger(DownloadManagerCache.class); @Inject @Delegate @Any private DownloadManager downloadManager; @Resource(name="config/cache/timeout") private long timeout; private Long count; private long lastUpdate; @Override public Long getCount() { if (count == null || System.currentTimeMillis() > lastUpdate + timeout) { count = downloadManager.getCount(); lastUpdate = System.currentTimeMillis(); } else { logger.info("Loading counter from cache..."); } return count; } }
Partiamo intanto col notare che la classe è astratta: questo ci dà la libertà di implementare solo i metodi che effettivamente vogliamo decorare! Definiamo poi la classe come decoratore tramite l’apposita annotazione @Decorator
. Eclipse ci informerà immediatamente che ogni decoratore deve avere uno ed un solo attributo annotato con @Delegate
: questo sarà il riferimento alla classe che stiamo decorando. Accompagnando poi questa annotazione con @Inject
e @Any
, stiamo dicendo al BeanManager CDI che il decoratore è valido per ogni implementazione dell’interfaccia DownloadManager
. L’attributo count farà da cache al contatore mentre timeout e lastUpdate serviranno per valutare la validità della cache stessa.
Tralasciando per il momento l’annotazione su timeout e dedichiamoci al metodo getCount. Esso semplicemente controllerà che la cache sia stata inizializzata o che sia ancora valida: in caso affermativo, restituirà il valore di count, altrimenti lo popolerà chiamando il metodo del delegato (cioè quello decorato!) e aggiornando l’attributo lastUpdate.
Prima di partire
Bene ma il decoratore che ciclo di vita ha? Ne ha uno proprio? Ebbene no! Il decoratore assume il ciclo di vita della singola istanza della classe che decora. Che significa? Che se ho un bean in @ApplicationScoped
, anche il decoratore avrà lo stesso scope (e così via). Una paio di curiosità: se ho due bean @ApplicationScoped
che implementano la stessa interfaccia, le istanze del decoratore saranno due, una per ogni implementazione. Le eventuali variabili di stato del decoratore faranno quindi riferimento alla specifica implementazione che decorano.
Inoltre, i bean da decorare devono essere gestiti dal BeanManager di CDI, purtroppo non funziona sugli EJB 🙁
Cosa manca quindi per far funzionare l’esempio?
- l’implementazione di
DownloadManager
deve essere@ApplicationScoped
- è necessario attivare il decoratore registrandolo nel file META-INF/beans.xml (o WEB-INF/beans.xml nei progetti web)
it.cosenonjaviste.DownloadManagerCache - definiamo il valore di timeout. Poteva essere inserito tranquillamente qui nel codice, ma per mettere ancora un po’ di carne al fuoco rendiamolo configurabile! In realtà questo banale esempio ci mostra come i bean CDI siano perfettamente integrati con tutte le funzionalità dell’Application Server… Basta inserire per esempio una env-entry nel web.xml ed il gioco è fatto:
timeout cache config/cache/timeout java.lang.Long 86400000 Il valore corrisponde ad un giorno in millisecondi.
Conclusioni
Abituati a sentire di AOP e interceptors, Java EE 6 porta una ventata di “novità”, se così si può dire, integrando nativamente i decoratori dotandoli di una veste più moderna. Sta a noi utilizzatori riuscire a capire se effettivamente era una cosa che ci mancava o se potevamo farne a meno: come si dice… ai posteri l’ardua sentenza!