SeamFramework.orgCommunity Documentation
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:
Primero, el administrador de Web Bean llama al constructor de Web Bean, para obtener una instancia del Web Bean.
Luego, el administrador de Web Bean inicializa los valores de los campos inyectados del Web Bean.
Más tarde, el administrador de Web Bean llama a todos los métodos inicializadores del Web Bean.
Por último, se llama al método de Web Bean @PostConstruct
, si existe.
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:
permitiendo al cliente seleccionar la aplicación requerida mediante anotaciones de enlace,
permitiendo al desplegador de aplicación seleccionar la aplicación apropiada para una despliegue particular, sin cambios en el cliente, habilitando o inhabilitando los tipos de despliegue, o
permitiendo que una implementación de una API remplace otra implementación de la misma API en el momento del despliegue, sin cambios al cliente, mediante prioridad de tipo de despliegue.
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;
}
Las anotaciones de enlace pueden tener miembros:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayBy {
PaymentType value();
}
En cuyo caso, el valor de miembro es importante:
@PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor;
@PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;
Se puede pedir al administrador de Web Bean que ignore a un miembro de un tipo de anotación de enlace anotando al miembro @NonBinding
.
Un punto de inyección puede incluso especificar múltiples anotaciones de enlace:
@Asynchronous @PayByCheque PaymentProcessor paymentProcessor
En este caso, sólo el Web Bean que tiene ambas anotaciones de enlace sería elegible para inyección.
Incluso los métodos de productor pueden especificar anotaciones de enlace:
@Produces
@Asynchronous @PayByCheque
PaymentProcessor createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
return new AsynchronousPaymentProcessor(processor);
}
Web Beans define un tipo de enlace @Current
, el cual es el tipo de enlace predeterminado para cualquier punto de inyección o Web Bean que no especifique explícitamente un tipo de enlace.
Hay dos circunstancias comunes en que se necesita especificar explícitamente a @Current
:
en un campo, para declararlo como un campo inyectado con el tipo de enlace por defecto, y
en un Web Bean, el cual tiene otro tipo de enlace además del tipo de enlace predeterminado.
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?
Web Beans define dos tipos de despliegue incorporados: @Production
y @Standard
. Por defecto, sólo los Web Beans con tipos de despliegue incorporados se habilitan cuando se despliega el sistema. Podemos identificar tipos de despliegue adicionales en un despliegue particular listándolos en web-beans.xml
.
Volviendo a nuestro ejemplo, cuando desplegamos nuestras pruebas de integración, deseamos que todos nuestros objetos @Mock
sean instalados:
<WebBeans>
<Deploy>
<Standard/>
<Production/>
<test:Mock/>
</Deploy>
</WebBeans
>
Ahora el administrador de Web Bean identificará e instalará todos los Web Beans anotados @Production
, @Standard
o @Mock
en el momento del despliegue.
El tipo de despliegue @Standard
es utilizado únicamente por algunos Web Beans especiales definidos por la especificación de Web Beans. No podemos utilizarlo para nuestros propios Web Beans ni inhabilitarlo.
El tipo de despliegue @Production
es el tipo de despliegue predeterminado para Web Beans que no declaran explícitamente un tipo de despliegue, y que se puede inhabilitar.
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.
Los tipos de despliegue son útiles para toda clase de cosas, algunos ejemplos a continuación:
Tipos de despliegue @Mock
y @Staging
para pruebas
@AustralianTaxLaw
para Web Beans de sitio específico
@SeamFramework
, @Guice
para marcos de terceras partes generados en Web Beans
@Standard
para Web Beans estándar definidos por la especificación Web Beans
Estamos seguros que puede pensar en más aplicaciones...
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:
las clases que son declaradas final
o tienen un método final
,
las clases que no tienen un constructor no-privado sin parámetros y
matrices y tipos primarios.
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:
La categoría de registro para un Logger
depende de la clase de objeto que lo posea.
La inyección de un parámetro HTTP o valor de encabezado depende del parámetro o del nombre de encabezado especificado en el punto de inyección.
La inyección del resultado de una prueba de expresión EL depende de la expresión que fue especificada en el punto de inyección.
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 <T extends Annotation
> T getAnnotation(Class<T
> annotation);
public Set<T extends Annotation
> getAnnotations();
}