Secondo articolo della serie su Web Beans, un insieme di servizi per Java EE nati per rendere più agevole lo sviluppo di applicazioni. Stavolta focalizzeremo l‘attenzione sugli scope dei WebBeans, che determinano, tra l‘altro, il ciclo di vita delle istanze.
Scopes
A differenza dei managed beans di JSF, altri componenti Java EE, quali ad esempio Servlet, EJB e JavaBean, non hanno uno scope ben definito. I componenti appena citati possono essere o singleton (e quindi in questo caso il loro stato è condiviso da tutti i client) o stateless object oppure oggetti che devono essere esplicitamente creati e distrutti dai loro client. Invece gli oggetti aventi uno scope esistono in un context avente un ciclo di vita ben definito. Essi possono essere creati in maniera automatica quando se ne ha bisogno e distrutti, sempre in maniera automatica, quando il context nel quale sono stati creati termina. Inoltre il loro stato viene automaticamente condiviso da tutti i client in esecuzione nello stesso context.
Tutti i Web Bean hanno uno scope. Questo ne determina il ciclo di vita delle istanze e quali fra queste devono essere visibili alle istanze degli altri Bean. Uno scope type viene rappresentato tramite una annotation.
Le specifiche Web Beans prevedono diversi scope type built-in. È possibile comunque estendere il set di tali scope type.
Scopes e contexts
Ad ogni scope type è associato un context object. Un context object è un’istanza dell’interfaccia javax.context.Context:
public interface Context { public Class<? extends Annotation> getScopeType(); public T get(Contextual bean); public T get(Contextual bean, CreationalContext creationalContext); boolean isActive(); }
Un Context viene invocato dal container (anche tramite framework terze parti), ma mai direttamente dall’applicazione. Un context object è responsabile della creazione e distruzione delle istanze di javax.Context.Contextual. Il metodo get() può restituire o l’istanza esistente di un dato tipo di Contextual oppure null, nel caso in cui non venga specificato alcun javax.Context.CreationalContext, oppure, nel caso in cui venga specificato il CreationalContext, una nuova istanza del type di javax.Context.Contextual indicato, creata invocando il metodo Contextual.create(). L’implementazione di Context è responsabile anche della distruzione delle istanze di Contextual da esso create. Queste vengono distrutte tramite invocazione del metodo Contextual.destroy().
In alcuni momenti dell’esecuzione di una applicazione, uno scope potrebbe essere inattivo rispetto al thread corrente. Quando uno scope è inattivo, qualsiasi invocazione del metodo get() scatena una eccezione di tipo javax.context.ContextNotActiveException. Il metodo isActive() consente di capire se in un determinato momento uno scope è attivo o inattivo.
La maggior parte degli scope è normal. Il context object di un normal scope è il mapping fra ciascun contextual type lecito per lo scope e un’istanza di tale contextual type. Per un thread non può essere mappata più di una istanza di un dato contextual type. L’insieme di tutte le istanze dei contextual type associati ad un dato scope per un dato thread viene indicato come context per quello scope relativamente a quel thread. Un context può essere associato ad uno o più thread. Si dice che esso si è propagato quando, passando da un punto ad un altro all’interno dell’esecuzione di un’applicazione, l’insieme di istanze di contextual type associate allo scope non viene alterato. Il context associato al thread corrente viene detto current context per lo scope. L’istanza mappata di contextual type associata al current context è detta current instance di quel contextual type. Il metodo Context.get(), in caso di normal scope attivo, restituisce la current instance del contextual type indicato. Nel momento in cui un context viene distrutto vengono distrutte anche tutte le istanze mappate di contextual type a quello scope.
Gli scope che non sono normal vengono detti pseudo-scope. Devono essere dichiarati esplicitamente tramite l’annotation
@ScopeType(normal=false)
Un esempio di pseudo-scope built-in è lo scope type @javax.context.Dependent. I Bean nella cui dichiarazione viene specificato questo scope type hanno un comportamento diverso da quelli dichiarati specificando gli altri scope type built-in:
- le istanze del Bean iniettate non sono mai shared tra i vari punti di injection;
- ogni istanza del Bean iniettata è limitata al ciclo di vita del Bean, della Servlet o dell’EJB in cui è stata iniettata;
- ogni istanza del Bean utilizzata per valutare una espressione in Unified Expression Language (EL) esiste solamente allo scopo di eseguire la valutazione;
- ogni istanza del Bean che riceve una invocazione di un metodo producer o observer esiste solamente per tale invocazione.
Un @Dependent scope è sempre inactive tranne che nei seguenti casi:
- quando un’istanza del Bean avente tale scope viene creata dal container per ricevere l’invocazione di un metodo producer o observer;
- mentre viene valutata una espressione EL;
- mentre viene invocato un metodo observer;
- mentre il container sta creando o distruggendo un’istanza di contextual del Bean o mentre sta iniettandogli le sue dipendenze;
- mentre il container sta iniettando le dipendenze di un EJB o di una Servlet o quando il container EJB invoca la callback @PostConstruct o @PreDestroy.
Definire nuovi scope types
Come già accennato in precedenza, uno scope type è una Java annotation. È definita come @Target({TYPE, METHOD, FIELD}) e @Retention(RUNTIME). Inoltre tutti gli scope types devono specificare la meta-annotation @javax.context.ScopeType. L’esempio seguente rappresenta la definizione di uno scope type custom:
@Inherited @ScopeType @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface BusinessProcessScoped {}
Lo scope di un Bean viene definito annotando la classe del Bean o il metodo producer con uno scope type. È possibile specificare una sola scope type annotation. In caso contrario il container scatena una javax.inject.DefinitionException in fase di deploy.
Vediamo un esempio di utilizzo di alcuni scope type built-in (@javax.context.RequestScoped, @javax.context.SessionScoped, @javax.context.ConversationScoped):
@RequestScoped public class ProductList implements DataModel { ... } public class Shop { @Produces @SessionScoped @WishList public List getWishList() { ..... } @Produces @ConversationScoped @ShoppingCart public List getShoppingCart() { ..... } }
Stessa cosa va fatta per quanto riguarda l’utilizzo di custom scope types. Nel caso dello scope type @BusinessProcessScoped definito nell’esempio precedente:
@BusinessProcessScoped public class Order { ... }
Se invece un Bean viene dichiarato nel file bean.xml, lo scope va specificato come tag XML avente il nome dello scope annotation type. Esempio:
In questo caso, qualora per un Bean nel file XML venga dichiarato più di uno scope, in fase di deploy il container scatenerà una eccezione di tipo javax.inject.DefinitionException. Se per un Bean non viene esplicitamente dichiarato uno scope (tramite annotation o tramite XML), lo scope di un Bean sarà quello di default. In questi casi il default scope dipende dagli stereotipi dicharati per il Bean. Gli stereotipi saranno uno degli argomenti trattati nel prossimo articolo.
Conclusioni
In questo secondo articolo abbiamo continuato la trattazione delle caratteristiche principali dei Web Beans. In particolare ci siamo soffermati sugli scope e sulle loro relazioni con il context. Nel prossimo articolo, prima di passare a qualche esempio concreto, termineremo la descrizione delle caratteristiche, concentrando l’attenzione sui deployment types e sugli stereotipi.
Riferimenti
[1] JSR-299 Expert Group, “JSR-299: Context and Dependency Injection for Java”