SeamFramework.orgCommunity Documentation

Capitolo 4. Dependency injection

4.1. Annotazioni di binding
4.1.1. Annotazioni di binding con membri
4.1.2. Combinazioni di binding annotation
4.1.3. Binding annotation e metodi produttori
4.1.4. Il tipo di binding predefinito
4.2. Tipi di deploy
4.2.1. Abilitazione dei tipi di deploy
4.2.2. Precedenza del tipo di deploy
4.2.3. Esempio dei tipi di deploy
4.3. Risoluzione di dipendenze non soddisfatte
4.4. Client proxy
4.5. Ottenere un riferimento a un Web Bean via codice
4.6. Chiamare al ciclo di vita, @Resource, @EJB e @PersistenceContext
4.7. L'oggetto InjectionPoint

Web Beans supporta tre meccanismi primari per la dependency injection:

Iniezione dei parametri del costruttore

public class Checkout {

        
    private final ShoppingCart cart;
    
    @Initializer
    public Checkout(ShoppingCart cart) {
        this.cart = cart;
    }
}

Iniezione dei parametri del metodo inizializzatore (initializer method):

public class Checkout {

        
    private ShoppingCart cart;
    @Initializer 
    void setShoppingCart(ShoppingCart cart) {
        this.cart = cart;
    }
    
}

Iniezione diretta degli attributi

public class Checkout {


    private @Current ShoppingCart cart;
    
}

La dependency injection avviene sempre quando il Web Bean viene istanziato la prima volta.

L'iniezione dei parametri del costruttore non è supportata per gli EJB, poiché gli EJB sono istanziati dal container EJB, non dal manager Web Bean.

I parametri dei costruttori e dei metodi di inizializzazione non devono essere annotati esplicitamente quando il tipo del binding è @Current, quello predefinito. I campi iniettati, comunque, devono specificare il tipo del binding, anche quando il tipo del binding è quello predefinito. Se il campo non specifica il tipo del binding, non verrà iniettato.

I metodi produttori supportano anche l'iniezione dei parametri:

@Produces Checkout createCheckout(ShoppingCart cart) {

    return new Checkout(cart);
}

Infine, i metodi observer (che vedremo in Capitolo 9, Eventi), i metodi disposal e i metodi distruttori supportano tutti l'iniezione dei parametri.

Le specifiche Web Beans definiscono una procedura, chiamata typesafe resolution algorithm (algoritmo di risoluzione sicura rispetto ai tipi), che il manager Web Bean segue quando deve identificare il Web Beanda iniettare in punto di iniezione. Questo algoritmo di primo acchito sembra complesso, ma una volta che lo si è compreso, in realtà, risulta piuttosto intuitivo. La risoluzione sicura dei tipi viene eseguita durante l'inizializzazione del sistema (system initialization time), il che significa che il manager Web Bean informerà immediatamente un utente se le dipendenze di un Web Bean non possono essere soddisfatte, lanciando una UnsatisfiedDependencyException o una AmbiguousDependencyException.

Lo scopo di questo algoritmo è di permettere a più Web Bean di implementare la stessa tipo definito dall'API e:

Indaghiamo come il manager di Web Beans individua un Web Bean da iniettare.

Se esiste più di un Web Bean che implementa un particolare tipo di API, il punto di iniezione può specificare esattamente quale Web Bean dovrebbe essere iniettato usando una binding annotation. Per esempio, ci potrebbero essere due implementazioni di PaymentProcessor:

@PayByCheque

public class ChequePaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}
@PayByCreditCard

public class CreditCardPaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}

Dove @PayByCheque e @PayByCreditCard sono binding annotation:

@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCreditCard {}

Lo sviluppatore di un Web Bean client usa la binding annotation per specificare esattamente quale Web Bean debba essere iniettato.

Utilizzando l'iniezione a livello di campo:

@PayByCheque PaymentProcessor chequePaymentProcessor;

@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;

Utilizzando l'iniezione a livello di metodo inizializzatore:

@Initializer

public void setPaymentProcessors(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                                 @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

O usando l'iniezione a livello di costruttore:

@Initializer

public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

Tutti i Web Bean hanno un tipo di deployment (deployment type). Ogni tipo di deployment identifica un insieme di Web Bean che dovrebbe essere installato in modo condizionale in corrispondenza ad alcuni deploy del sistema.

Per esempio, potremmo definire un tipo di deploy denominato @Mock, che identifichi i Web Bean da installare soltanto quando il sistema è posto in esecuzione in un ambiente di test integrato:

@Retention(RUNTIME)

  @Target({TYPE, METHOD})
  @DeploymentType
  public @interface Mock {}

Supponiamo di avere alcuni Web Bean che interagiscano con un sistema di pagamenti esterno:

public class ExternalPaymentProcessor {

        
    public void process(Payment p) {
        ...
    }
    
}

Dal momento che questo Web Bean non specifica esplicitamente un tipo di deploy, ha il tipo di deploy predefinito @Production.

Per le attività di test (d'unità o integrazione), il sistema esterno è lento o non disponibile. Così sarebbe necessario creare un oggetto mock:

@Mock 

public class MockPaymentProcessor implements PaymentProcessor {
    @Override
    public void process(Payment p) {
        p.setSuccessful(true);
    }
}

Ma in che modo il manager Web Bean determina quale implementazione usare con un particolare deploy?

Se avete prestato attenzione, vi state probabilmente chiedendo come il manager Web Bean decida quale implementazione scegliere — ExternalPaymentProcessor o MockPaymentProcessor — Si consideri cosa succede quando il manager incontra questo punto di iniezione:

@Current PaymentProcessor paymentProcessor

Vi sono ora due Web Bean che soddisfano l'interfaccia di PaymentProcessor. Naturalmente, non è possibile utilizzare una binding annotation per eliminare l'ambiguità, poiché le binding annotation sono cablate nel sorgente in corrispondenza al punto di iniezione, e noi vogliamo che il manager sia in grado di decidere a deployment time!

La soluzione a questo problema sta nel fatto che ciascun tipo di deploy ha una diversa precedenza. La precedenza dei tipi di deploy è determinata dall'ordine con cui appaiono in web-beans.xml. Nel nostro esempio, @Mock compare dopo @Production cosicché ha una precedenza più alta.

Ogni volta che il manager scopre che più di un Web Bean potrebbe soddisfare il contratto (interfaccia più binding annotation) specificato da un punto di iniezione, passa a considerare la precedenza relativa dei Web Bean. Se uno ha una precedenza superiore a quella degli altri, questo viene scelto per essere iniettato. Così, nel nostro esempio, il manager Web Bean inietterà MockPaymentProcessor quando viene eseguito nel nostro ambiente di test (che è esattamente ciò che vogliamo).

E' interessante confrontare questa funzionalità con le architetture di gestone oggi in voga. Vari container "lightweight" permettono il deploy condizionale di classi che esistono nel classpath, ma le classi che devono essere installate devono essere elencate esplicitamente ed individualmente nel codice di configurazione o in qualche file XML di configurazione. Web Beans supporta certo la definizione e configurazione dei Web Bean attraverso l'XML, ma nei casi comuni in cui non si renda necessaria una configurazione complicata, i tipi di deploy permettono di abilitare un insieme completo di Web Bean con una sola linea di XML. Al contempo, uno sviluppatore che esamini il codice, potrà facilmente identificare gli scenari di deploy in cui il Web Bean sarà utilizzato.

L'algoritmo di risoluzione sicura rispetto ai tipi fallisce quando, dopo avere considerato le binding annotation e i tipi di deploy di tutti i Web Bean che implementano il tipo di un punto di iniezione, il manager Web Bean non è in grado di identificare esattamente uno ed un solo Web Bean da iniettare.

Di solito è semplice porre rimedio a un'eccezione UnsatisfiedDependencyException o AmbiguousDependencyException.

Per rimediare ad una UnsatisfiedDependencyException, si deve semplicemente fornire un Web Bean che implementi il tipo dell'API in uso e abbia gli stessi tipi di binding del punto di iniezione #151; o si deve abilitare il tipo di deploy di un Web Bean che già implementa il tipo dell'API in uso e possiede i tipi di binding in esame.

Per porre rimedio a una AmbiguousDependencyException, si deve introdurre un tipo di binding per distinguere tra le due implementazioni del tipo delle API, o si deve cambiare il tipo di deploy di una delle implementazione in modo che il manager Web Bean possa usare la precedenza dei tipi di deploy per scegliere fra di essi. Una AmbiguousDependencyException può verificarsi soltanto se due Web Bean condividono il tipo di binding e hanno esattamente lo stesso tipo di deploy.

Vi è un ulteriore questione di cui occorre essere a conoscenza quando si usa la dependency injection in Web Beans.

I client di un Web Bean che sono stati iniettati solitamente non hanno un riferimento diretto all'istanza del Web Bean.

Immaginiamo che un Web Bean associato allo scope applicazione tenga un riferimento diretto a un Web Bean associato allo scope richiesta. Il Web Bean con scope applicazione è condiviso fra molte diverse richieste. Comunque, ciascuna richiesta dovrebbe vedere una diversa istanza del Web bean con scope richiesta!

Immaginiamo ora che un Web Bean con scope sessione abbia un riferimento diretto a un Web Bean con scope applicazione . Ogni tanto, il contesto della sessione viene serializzato su disco in modo da usare la memoria in modo più efficiente. Comunque, l'istanza del Web Bean con scope applicazione non dovrebbe essere serializzato insieme al Web Bean con scope sessione!

Quindi, a meno che un Web Bean abbia lo scope predefinito @Dependent, il manager Web Bean deve rendere indiretti tutti i riferimenti al Web Bean iniettati attraverso un oggetto proxy. Questo client proxy ha la responsabilità di assicurare che l'istanza del Web Bean su cui viene invocato un metodo sia l'istanza associata al contesto corrente. Il client proxy, inoltre, permette ai Web Bean associati a contesti come quello di sessione di essere salvati su disco senza serializzare ricorsivamente altri Web Beans che siano stati iniettati.

Purtroppo, a causa di limitazioni del linguaggio Java, alcuni tipi Java non possono essere gestiti tramite un proxy dal manager Web Bean. Quindi, il manager Web Bean lancia un'eccezione UnproxyableDependencyException se il tipo di un punto di iniezione non può essere gestito tramite proxy.

I seguenti tipi Java non possono essere gestiti tramite proxy dal manager Web Bean:

Di solito è molto facile rimediare a una UnproxyableDependencyException. Si deve semplicimente aggiungere un costruttore privo di parametri alla classe iniettata, introdurre un'interfaccia, o modificare lo scope del Web Bean iniettato a @Dependent.

L'applicazione può ottenere un'istanza dell'interfaccia Manager attraverso iniezione:

@Current Manager manager;

L'oggetto Manager fornisce un insieme di metodi per ottenere l'istanza di un Web Bean via codice.

PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class);

Le binding annotation possono essere specificate come sottoclassi della classe helper AnnotationLiteral, poiché è altrimenti difficile istanziare un tipo annotazione in Java.

PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class, 

                                               new AnnotationLiteral<CreditCard
>(){});

Se il tipo di binding ha un membro annotazione, non è possibile utilizzare una sottoclasse anonima di AnnotationLiteral — sarà invece necessario creare una sottoclasse non anonima:

abstract class CreditCardBinding 

    extends AnnotationLiteral<CreditCard
> 
    implements CreditCard {}
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class, 

                                               new CreditCardBinding() { 
                                                   public void value() { return paymentType; } 
                                               } );

I Web Beans di tipo enterprise supportano tutte le callback del ciclo di vita definite dalle specifiche EJB: @PostConstruct, @PreDestroy, @PrePassivate e @PostActivate.

Semplici Web Bean supportano soltanto le callback @PostConstruct e @PreDestroy.

Sia i Web Bean semplici che quelli enterprise supportano l'uso di @Resource, @EJB e @PersistenceContext per l'iniezione rispettivamente di risorse Java EE, di EJB e di contesti di persistenza JPA. I Web Bean semplici non supportano l'uso di @PersistenceContext(type=EXTENDED).

La callback @PostConstruct viene sempre eseguita dopo che tutte le dipendenze sono state iniettate.

Certi tipi di oggetti dipendenti — Web Bean con scope @Dependent — hanno bisogno di avere informazioni riguardo l'oggetto o il punto in cui sono iniettati per fare quello che devono. Per esempio:

Un Web Bean con scope @Dependent può essere iniettato con un'istanza di InjectionPoint e accedere i metadati riguardanti il punto di iniezione cui appartiene.

Vediamo un esempio. Il codice seguente è prolisso e vulnerabile a problemi di refactoring:

Logger log = Logger.getLogger(MyClass.class.getName());

Questo piccolo e intelligente metodo produttore permette di iniettare un Logger JDK senza specificare esplicitamente la categoria di log:

class LogFactory {


   @Produces Logger createLogger(InjectionPoint injectionPoint) { 
      return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); 
   }
}

Ora è possibile scrivere:

@Current Logger log;

Non siete convinti? Eccovi un secondo esempio. Per iniettare parametri HTTP, è necessario definire un tipo di binding:

@BindingType

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
   @NonBinding public String value();
}

Potremmo usare questo tipo di binding in corrispondenza ai punti di iniezione in questo modo:

@HttpParam("username") String username;

@HttpParam("password") String password;

Il seguente metodo produttore esegue il lavoro:

class HttpParams


   @Produces @HttpParam("")
   String getParamValue(ServletRequest request, InjectionPoint ip) {
      return request.getParameter(ip.getAnnotation(HttpParam.class).value());
   }
}

(Occorre notare che il membro value() dell'annotazione HttpParam viene ignorato dal manager Web Bean poiché è annotato con @NonBinding.)

Il manager Web Bean fornisce un Web Bean di sistema che implementa l'interfaccia InjectionPoint:

public interface InjectionPoint { 

   public Object getInstance(); 
   public Bean<?> getBean(); 
   public Member getMember(): 
   public <extends Annotation
> T getAnnotation(Class<T
> annotation); 
   public Set<extends Annotation
> getAnnotations(); 
}