SeamFramework.orgCommunity Documentation

Capítulo 4. Inyección de dependencia

4.1. Anotaciones de Enlace
4.1.1. Anotaciones de enlace con miembros
4.1.2. Combinaciones de anotaciones de enlace
4.1.3. Anotaciones de enlace y métodos de productor
4.1.4. El tipo de enlace predeterminado
4.2. Tipos de despliegue
4.2.1. Habilitar tipos de despliegue
4.2.2. Prioridad de tipo de despliegue
4.2.3. Ejemplo de tipos de despliegue
4.3. Corregir dependencias insatisfechas
4.4. Los proxy de cliente
4.5. Obtención de un Web Bean por búsqueda programática
4.6. El ciclo de vida de los callbacks, @Resource, @EJB y @PersistenceContext
4.7. El objeto InjectionPoint

Web Beans soporta tres mecanismos primarios para inyección de dependencia:

Constructor de inyección de parámetro:

public class Checkout {

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

Inyección de parámetro del método Inicializador:

public class Checkout {

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

E inyección directa de campo:

public class Checkout {


    private @Current ShoppingCart cart;
    
}

La inyección de dependencia siempre se presenta cuando la instancia de Web Bean es instanciada primero:

La inyección de parámetro constructor no es admitida por beans de EJB, porque EJB es instanciado por el contenedor de EJB, no por el administrador de Web Bean.

Los parámetros de constructores y métodos de inicializador no necesitan ser anotados explícitamente cuando se aplique el tipo de enlace predeterminado @Current. Los campos inyectados, sin embargo, deben especificar un tipo de enlace, cuando se aplique el tipo de enlace predeterminado. Si el campo no especifica ningún tipo de enlace, no será inyectado.

Los métodos de productor también admiten inyección de parámetro:

@Produces Checkout createCheckout(ShoppingCart cart) {

    return new Checkout(cart);
}

Por último, los métodos de observador (que encontraremos en Capítulo 9, Eventos), los métodos desechables y los métodos destructores, admiten inyección de parámetro.

La especificación de Web Beans define un procedimiento, llamado algoritmo de resolución de typesafe que el administrador de Web Bean sigue al identificar el Web Bean para inyectar a un punto de inyección. Este algoritmo parece complejo en un principio, pero una vez que lo entienda, es en realidad muy intuitivo. La resolución de Typesafe se realiza al inicializar el sistema, lo que significa que el administrador informará al usuario inmediatamente si se pueden cumplir las dependencias de un Web Bean, produciendo una UnsatisfiedDependencyException o una AmbiguousDependencyException.

El propósito de este algoritmo es permitir a múltiples Web Beans implementar el mismo tipo API ya sea:

Exploremos cómo el administrador de Web Beans determina una Web Bean para ser inyectado.

Si tenemos más de un Web Bean que implemente un tipo determinado de API, el punto de inyección puede especificar el Web Bean que debe ser inyectado mediante una anotación de enlace. Por ejemplo, deberían haber dos aplicaciones del PaymentProcessor:

@PayByCheque

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

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

Donde @PayByCheque y @PayByCreditCard son anotaciones de enlace:

@Retention(RUNTIME)

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

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

Un desarrollador de cliente de Web Bean utiliza la anotación de enlace para especificar exactamente el Web Bean que debe inyectarse.

Uso de inyección de campo:

@PayByCheque PaymentProcessor chequePaymentProcessor;

@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;

Uso de inyección de método inicializador:

@Initializer

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

O uso de inyección de constructor:

@Initializer

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

Todos los Web Beans tienen un tipo de despliegue. Cada tipo de despliegue identifica un conjunto de Web Beans que debería ser instalado bajo condiciones en algunos despliegues del sistema.

Por ejemplo, podríamos definir un tipo de despliegue llamado @Mock, el cual identificaría Web Beans que deben ser instalados sólo cuando el sistema se ejecute dentro de un entorno de pruebas de integración:

@Retention(RUNTIME)

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

Supongamos que tenemos algunos Web Bean que interactuaron con un sistema externo para procesar pagos:

public class ExternalPaymentProcessor {

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

Como este Web Bean no especifica explícitamente un tipo de despliegue, tiene el tipo de despliegue predeterminado @Production.

Para prueba de integración o de unidad, el sistema externo está lento o no está disponible. Por lo tanto, creamos el objeto mock:

@Mock 

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

Pero, ¿cómo determina el administrador de Web Bean la aplicación que debe utilizar en un despliegue determinado?

Si ha prestado atención, probablemente se estará preguntando cómo escoge Web Bean la aplicación — ExternalPaymentProcessor o MockPaymentProcessor — . Piense en lo que sucede cuando el administrador encuentra este punto de inyección:

@Current PaymentProcessor paymentProcessor

Ahora hay dos Web Beans que cumplen el contrato PaymentProcessor. Claro está que no podemos utilizar una anotación de enlace para explicar, porque las anotaciones de enlace están codificadas dentro de la fuente en el punto de inyección, y queremos que el administrador pueda decidir en el ¡momento de despliegue!

La solución a este problema es que cada tipo de despliegue tiene una prioridad diferente. La prioridad de los tipos de despliegue es determinada por el orden de aparición en web-beans.xml. En nuestro ejemplo, @Mock es posterior a @Production por lo tanto tiene mayor prioridad.

Cada vez que el administrador descubre que más de un Web Bean cumple el contrato (tipo API más anotaciones de enlace) especificado por un punto de inyección, considera la prioridad relativa de los Web Beans. Se escoge el Web Bean que tiene prioridad respecto de los otros para inyectar. Por lo tanto, en nuestro ejemplo, el administrador de Web Bean inyectará MockPaymentProcessor al ejecutar en nuestro entorno de prueba de integración (que es precisamente lo que queremos).

Es interesante comparar esta facilidad con las arquitecturas populares del administrador de hoy. Varios contenedores "ligeros" también permiten el despliegue condicional de clases existentes en el classpath, pero las clases que van a ser desplegadas deben ser explícitamente, listadas de modo individual en el código de configuración o en algún archivo de configuración XML. Web Beans no admite definición de Web Bean ni configuración vía XML, pero en el común de los casos donde no se requiere una configuración compleja, los tipos de despliegue permiten habilitar un conjunto completo de Web Beans con una sóla línea de XML. Mientras tanto, un desarrollador que esté navegando el código puede fácilmente identificar en qué escenarios de despliegue se utilizará el Web Bean.

El algoritmo de resolución typesafe falla cuando, después de considerar las anotaciones de enlace y los tipos de despliegue de todos los Web Beans que implementan el tipo API de un punto de inyección, el administrador de Web Bean no puede identificar con precisión un Web Bean para inyectar.

Por lo general es fácil corregir una UnsatisfiedDependencyException o una AmbiguousDependencyException.

Para corregir una UnsatisfiedDependencyException, basta con proporcionar un Web Bean que implemente el tipo API y tenga los tipos de enlace del punto de inyección — o permita el tipo de despliegue de un Web Bean que ya implemente el tipo API y tenga los tipos de enlace.

Para corregir una AmbiguousDependencyException, introduzca un tipo de enlace para distinguir entre las dos implementaciones del tipo de API o cambie el tipo de despliegue de una de las implementaciones con el fin de que el administrador de Web Bean pueda utilizar la prioridad de tipo de despliegue para escoger entre ellas. Una AmbiguousDependencyException sólo puede presentarse si dos Web Beans comparten un tipo de enlace y tienen exactamente el mismo tipo de despliegue.

Hay algo más que necesita saber cuando utilice inyección de dependencia en Web Beans.

Los clientes de un Web Bean inyectado no suelen mantener una referencia directa a una instancia de Web Bean.

Imagine que un Web Bean vinculado al ámbito de aplicación mantiene una referencia directa a un Web Bean vinculado al ámbito de petición. La aplicación en el ámbito de Web Bean es compartida entre muchas peticiones diferentes. No obstante, cada petición ¡debe ver una instancia diferente de la petición en el ámbito de WebBean!

Ahora imaginemos que un enlace de Web Bean a la sesión mantiene una referencia directa a un Web Bean enlazado al ámbito de la aplicación. De vez en cuando, el contexto de sesión se serializa al disco con el fin de utilizar la memoria de un modo más eficiente. Sin embargo, la aplicación en el ámbito de la instancia de Web Bean ¡no debe serializarse junto con la sesión en el ámbito de Web Bean!

Por lo tanto, a menos que un Web Bean tenga un ámbito predeterminado @Dependent, el administrador de Web Bean deberá direccionar indirectamente todas las referencias inyectadas al Web Bean a través del objeto de proxy. Este proxy de cliente responsable de garantizar que la instancia de Web Bean reciba un método de invocación es la instancia asociada con el contexto actual. El proxy de cliente también permite a los Web Beans vincularse a contextos tales como el contexto de sesión que debe serializarse al disco sin serializar de modo recursivo a otros Web Beans inyectados.

Lamentablemente, debido a limitaciones del lenguaje de Java, el administrador de Web Bean no puede utilizar proxy en algunos tipos de Java. Por lo tanto, el administrador de Web Bean produce un UnproxyableDependencyException si no se puede aplicar proxy al tipo de un punto de inyección.

El administrador de Web Bean no puede aplicar proxy en los siguientes tipos de Java:

Suele ser muy fácil corregir una UnproxyableDependencyException. Basta con añadir un constructor sin parámetros a la clase inyectada, introducir una interfaz, o cambiar el ámbito del Web Bean inyectado a @Dependent.

La aplicación puede obtener una instancia de la interfaz Manager a través de inyección:

@Current Manager manager;

El objeto Manager proporciona un grupo de métodos para obtener una instancia de Web Bean en forma programática.

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

Las anotaciones de enlace se pueden especificar a través de subclasificaciones de la clase auxiliar AnnotationLiteral, porque de otra manera es difícil instanciar un tipo de anotación en Java.

PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class, 

                                               new AnnotationLiteral<CreditCard
>(){});

Si el tipo de vinculación tiene un miembro de anotación, no podemos utilizar una subclase anónima de AnnotationLiteral — en su lugar, necesitaremos crear una subclase llamada:

abstract class CreditCardBinding 

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

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

Los Web Beans de empresa admiten todos los ciclos de vida de las devoluciones de llamadas definidas por la especificación EJB: @PostConstruct, @PreDestroy, @PrePassivate y @PostActivate.

Los Web Beans sencillos admiten únicamente las devoluciones de llamadas @PostConstruct y @PreDestroy.

Web Beans sencillos y empresariales soportan el uso de @Resource, @EJB y @PersistenceContext para inyección de recursos de Java EE, EJB y contextos de persistencia JPA, respectivamente. Web Beans sencillos no admiten el uso de @PersistenceContext(type=EXTENDED).

La devolución de llamada @PostConstruct siempre se presenta tras la inyección de todas las dependencias.

Hay algunas clases de objetos — con ámbito @Dependent — que necesitan saber algo acerca del objeto o punto de inyección dentro del cual son inyectados para poder hacer lo que hacen. Por ejemplo:

Un Web Bean con ámbito @Dependent puede inyectar una instancia de InjectionPoint y acceder a metadatos relativos al punto de inyección al que pertenezca.

Veamos un ejemplo. El código siguiente es detallado, y vulnerable a problemas de refactorización:

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

Este método inteligente de productor permite inyectar un Logger JDK sin especificar explícitamente la categoría de registro:

class LogFactory {


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

Ahora podemos escribir:

@Current Logger log;

¿No está convencido? Entonces, veamos un segundo ejemplo. Para inyectar parámetros, necesitamos definir el tipo de vinculación:

@BindingType

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

Utilizaríamos este tipo de enlace en puntos de inyección, como a continuación:

@HttpParam("username") String username;

@HttpParam("password") String password;

El siguiente método de productor sí funciona:

class HttpParams


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

(Observe que el miembro del valor() de la anotación HttpParam es ignorado por el administrador de Web Bean porque está anotado como @NonBinding.)

El administrador de Web Bean proporciona un Web Bean incorporado que implementa la interfaz 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(); 
}