EJB 3.1 Singleton: l’ultimo arrivato nella famiglia EJB 3

Un piccolo passo per EJB3, un grande passo per l’uomo: EJB ha incrementato leggermente la sua versione con la specifica Java EE 6 introducendo qualche lieve modifica, d’altro canto il grande salto lo aveva già fatto! In questa nuova versione sembra che la tendenza sia quella della semplificazione architetturale: per esempio un Session Bean non deve più implementare un’interfaccia (non so se è meglio, comunque de gustibus…), oppure è possibile inserire direttamente il modulo EJB nella cartella WEB-INF/lib del progetto web, abilitando un servlet container come Tomcat al mondo degli EJB. La novità più clamorosa comunque è un nuovo tipo di session bean: il Singleton!

Sing-che?!

Per chi non lo sapesse, il Singleton è uno dei pattern storici dell’ingegneria del software formalizzati dalla Gang of Four e risponde alla seguente definizione:

Il Singleton è un pattern creazionale (che concerne la creazione degli oggetti) che ha lo scopo di garantire che di una determinata classe venga creata una e una sola istanza, fornendo un punto di accesso globale a tale istanza.

Come si implementa una caratteristica di questo tipo? Per tutti quei linguaggi che supportano variabili statiche (vedi C++, Java, Php…) la risposta è sempre la stessa: la classe di cui vogliamo avere una sola istanza avrà un costruttore privato e un factory method (ovviamente statico) sarà responsabile di istanziarla solo la prima volta che viene chiamato, mentre le altre volte restituirà sempre la stessa istanza. Per tutti i dettagli, vi consigliamo di approfondire questo e gli altri pattern sulla bibbia: “Design Patterns: Elements of Reusable Object-Oriented Software” che però è in inglese e implementa i pattern in C++. Esiste anche una traduzione in Italiano e in Java intitolata “Design pattern in Java. Manuale pratico“.

Il buono, il brutto e il cattivo (a.k.a. il Web, il Singleton e il Cluster)

Finché lavoriamo con applicazioni stand-alone, il singleton si rivela un pattern molto importante, anche se va usato con parsimonia. Nel mondo web poi, se mal usato, può essere addirittura pericoloso: ricordiamoci che è una istanza unica a livello di applicazione, di conseguenza trascende sessioni utente, non è thread-safe se mantiene un proprio stato (a meno di botte di sinchronized) e quant’altro. E se siamo in un cluster? Ecco la fregatura! Il cluster non prevede nessun meccanismo di serializzazione delle variabili statiche (sono a livello di classe, mica di istanza per cui perché sincronizzarle??) e quindi come potete intuire accade che il singleton è sì un’unica istanza, ma una per nodo del cluster!!

Singleton Session Bean

Ecco che la specifica EJB 3.1 ci viene in aiuto con un nuovo tipo di Session Bean, il Singleton! Com’è implementato? Essenzialmente può essere paragonato ad un Session Bean Stateless che sta in un pool di dimensione 1. Ovviamente non deve essere senza stato, infatti può essere invocato in modo concorrente da thread diversi. Supporta tutte le caratteristiche degli EJB come i metodi di callback @PostConstruct e @PreDestroy, che vengono chiamati rispettivamente al primo utilizzo dopo l’avvio dell’applicazione, (inizializzazione lazy) e al suo spegnimento. A differenza degli altri ha a disposizione una annotazione @Startup a livello di classe per forzare l’inizializzazione all’avvio dell’applicazione (inizializzazione eager) e @DependsOn per dare un ordine di inizializzazione di più singleton. Usando queste annotazioni insieme si riescono a creare dei veri e propri listener sul ciclo di vita di una applicazione.

Vediamo adesso un po’ di codice. Immaginiamo di aver bisogno di un generatore di numeri sequenziali per identificare univocamente certe entità (cioè simuliamo un sequence del database…):

@Singleton
@Startup
public class SequenceManagerEJB implements SequenceManagerEJBLocal {

   private static final Logger logger = 
      Logger.getLogger(SequenceManagerEJB.class);
   
   @PersistenceContext(unitName = "myPU")
   private EntityManager entityManager;
   
   private long count;
   
   @PostConstruct
   protected void initSingleton() {
      logger.info("Init singleton");
      count = (Long) this.entityManager.createNamedQuery(
        "getMaxValue").getSingleResult() + 1;
   }
   
   @Override
   public long nextVal() {
      return count++;
   }
   
   @PreDestroy
   protected void dispose() {
      logger.info("Destroing singleton");
   }
}

Al termine dell’avvio del modulo EJB verrà inizializzato il singleton per via dell’annotazione @Startup: il metodo di callback initSingleton() provvederà a sincronizzare il contatore in memoria con il massimo valore che trova in tabella per quella entità (tramite la named query getMaxValue). Da questo momento in poi ogni nuovo valore verrà ottenuto chiamando nextVal.

Singleton e concorrenza

Con una classe di questo tipo usata da tutti gli altri EJB del sistema, chi mi garantisce che la concorrenza sia gestita correttamente e due entità non ricevano lo stesso valore? Per definizione l’EJB Singleton è thread safe, tuttavia è possibile controllare la cosa.

Il singleton supporta due modelli di accesso concorrente:

  1. Container-Managed Concurrency tramite @ConcurrencyManagement(CONTAINER) (annotazione di default). La concorrenza è gestita con il lock al momento dell’accesso al metodo o della classe da parte di un thread. Il tipo di lock può essere di due tipi:
    1. @Lock(WRITE): il bean diventa praticamente single-thread. E’ il comportamento di default e garantisce consistenza dei dati, per questo nel codice mostrato non è presente.
    2. @Lock(READ): nel caso in cui si è sicuri che un metodo non effettui scritture (un getter per esempio) si può evitare che il bean diventi un collo di bottiglia inutilmente. E’ possibile per esempio impostare il read-lock a livello di oggetto e usare il write-lock solo a livello dei metodi che effettuano scritture.

    Per evitare deadlock, è stata introdotta anche l’annotazione @AccessTimeout per indicare, a livello di classe o metodo, quanto tempo un thread deve attendere prima di acquisire il lock. Il valore di default dipende dai vendor.

  2. Bean-Managed Concurrency tramite l’annotazione @ConcurrencyManagement(BEAN). Il container non si preoccupa della gestione della concorrenza che è demandata all’implementazione dell’utente che può scegliere come agire tra synchronized e javax.util.concurrent.

Conclusioni

Il nuovo tipo di EJB non solo risolve il problema delle istanza multiple del pattern singleton in cluster, ma introduce un nuovo tipo di bean che, inizializzato eager, può fare da vero e proprio listener sul ciclo di vita di una applicazione a livello business come fa il ServletContextListener per il modulo web.

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+