JCache fai-da-te con JBoss 7 e Infinispan

In un post precedente abbiamo già visto cos’è Infinispan e come si configura una cache con JBoss AS 7. In vista degli ultimi ritocchi a Java EE 7, Infinispan tende la mano a CDI con il nuovo modulo infinispan-cdi, fornendo al momento una parziale implementazione alla specifica JCache (JSR-107) che, come suggerisce il nome, regolamenterà la gestione della cache nelle prossime applicazioni Java EE tramite semplici annotazioni (uno po’ come fa da poco Spring 3.1).

JCache: lavori in corso

Le annotazioni più importanti introdotte dalla specifica e implementate nel modulo infinispan-cdi sono:

  • @CacheResult: permette di salvare nella cache il risultato di un metodo;
  • @CachePut: salva il parametro di un metodo;
  • @CacheRemoveEntry: rimuove un elemento dalla cache;
  • @CacheRemoveAll: rimuove tutti gli elementi dalla cache.

e si usano così:

public class UserDAO {
   @CacheResult(cacheName="user-cache")
   User getUser(long id){...};
 
   @CachePut(cacheName="user-cache")
   void storeUser(long id, @CacheValue User user){...};
 
   @CacheRemoveEntry(cacheName="user-cache")
   void removeUser(long id){...};
  
   @CacheRemoveAll(cacheName="user-cache")
   void removeAllUser(){...};
}

In puro stile CDI, queste annotazioni corrispondono a degli Interceptors, da registrare nel nostro bean.xml per essere attivati:

<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
 <interceptors>
  <class>org.infinispan.cdi.interceptor.CacheResultInterceptor</class>
  <class>org.infinispan.cdi.interceptor.CachePutInterceptor</class>
  <class>org.infinispan.cdi.interceptor.CacheRemoveEntryInterceptor</class>
  <class>org.infinispan.cdi.interceptor.CacheRemoveAllInterceptor</class>
</interceptors>
</beans>

Sembra tutto perfetto…ma allora qual è il problema? Il fatto è che il modulo è ancora in fase di sviluppo e la specifica stessa di JCache non è ben definita: basti pensare che le interfacce del package javax.cache sono cambiate diverse volte negli ultimi mesi; è possibile farsi un’idea della vivacità dello sviluppo dando un’occhiata alla home del progetto su GitHub.

Un altro problema che ho riscontrato sono le innumerevoli dipendenze: anche utilizzando Maven, non sono riuscito a ottenere buoni risultati provando ad integrare il tutto in JBoss 7.1.1. Inoltre, 20 jar per far funzionare una cache mi sono sembrati un po’ troppi.

Alla fine mi son detto: a me interessa solo @CacheResult (e anche subito 😉 ) per cui rimango sintonizzato in attesa di nuovi sviluppi e intanto mi reinvento la ruota ( 🙁 ) prendendo spunto da quanto ho capito come dovrebbe funzionare l’implementazione di JCache.

@CacheResult fai-da-te

Preso quindi da un impeto demiurgico, ecco quello che serve:

  • una cache configurata (abbiamo già visto come);
  • un interceptor che conterrà la cache;
  • una annotazione per identificare l’interceptor, che chiameremo @DailyCacheResult, in onore alla cache creata nel post precedente.

La coppia Interceptor/Interceptor Binding sarà del tipo:

@InterceptorBinding
@Inherited
@Target(
{ TYPE, METHOD })
@Retention(RUNTIME)
@Documented
public @interface DailyCacheResult {}

@Interceptor
@DailyCacheResult
public class DailyCacheInterceptor {
   @Inject
   @DailyCache
   private Cache<String, Object> cache;

   @AroundInvoke
   public Object manage(InvocationContext ic) throws Exception {
      String cacheKey = createKey(ic);

      if (cache.containsKey(cacheKey) && (cacheKey != null)) {
         logger.info("Values from cache for key: " + cacheKey);
         return cache.get(cacheKey);
      }
      else {
         Object result = ic.proceed();
         if (result != null) {
            cache.put(cacheKey, result);
            logger.info("Caching values for key: " + cacheKey);
         }
         return result;
      }
   }
}

dove la chiave della cache, generata dal metodo createKey(ic), può essere costituita da:

nome package + nome classe + nome metodo + valori parametri del metodo

Applicando quindi l’annotazione @DailyCacheResult ad un metodo si ottiene il risultato sperato: la nostra cache giornaliera è stata correttamente iniettata tramite l’annotazione @DaiyCache. Non si poteva iniettare direttamente il CacheContainer nell’interceptor? Il fatto è che, almeno in JBoss 7.1.1, è bene regolare il ciclo di vita della cache “manualmente”, altrimenti ad ogni ripubblicazione dell’applicazione, senza riavviare il server, otteniamo un sacco di eccezioni in console. Questo potrebbe esser un buon caso d’uso per un EJB Singleton:

@Singleton
@LocalBean
@Startup
public class DailyCacheConfig {
   @Resource(lookup = "java:jboss/infinispan/container/expiration-cache")
   private CacheContainer cacheContainer;

   @PostConstruct
   void startUp() {
      logger.info("Starting daily-cache...");
      getDailyCache().start();
   }

   @PreDestroy
   void shutDown() {
      logger.info("Stopping daily-cache...");
      getDailyCache().stop();
   }

   @Produces
   @DailyCache
   public Cache<String, Object> getDailyCache() {
      return cacheContainer.getCache("daily-cache");
   }
}

All’avvio e alla chiusura dell’applicativo verranno chiamati rispettivamente i metodi annotati con @PostConstruct e @PreDestroy, permettendo di controllare lo stato di attività della cache. Infine, il metodo getDailyCache() farà da “factory” della nostra specifica cache, grazie al semplice qualifier:

@Qualifier
@Target(
{ TYPE, METHOD, PARAMETER, FIELD })
@Retention(RUNTIME)
@Documented
public @interface DailyCache {}

Conclusioni

Non è mai un bene reinventarsi la ruota, ma in certe situazioni ne siamo costretti. Questo è semplicemente un modo che ho trovato per sfruttare la cache di Infinispan senza rinunciare all’espressività che ci darà JCache spero a breve. Rimango quindi in attesa dei prossimi sviluppi per “tornare sulla retta via!”.

http://infinispan.blogspot.it/2011/09/when-infinispan-meets-cdi.html

https://docs.jboss.org/author/display/ISPN/CDI+Support

https://docs.jboss.org/author/display/ISPN/Getting+Started+Guide+-+JBoss+AS+7

http://gregluck.com/blog/archives/2011/07/start-using-jsr107s-jcache-api/

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+