En este tutorial vamos a ver aspectos más avanzados que nos permiten trabajar con conceptos como Alternativas, Especialización, Productores, Eventos e Interceptores en CDI.
0. Índice de contenidos.
- 1. Introducción
- 2. Entorno
- 3. Alternativas
- 4. Especialización
- 5. Productores
- 6. Eventos
- 7. Conclusiones
- 8. Referencias
1. Introducción
Como ya vimos en el anterior tutorial relacionado (Inyección de dependencias a través de CDI en Tomcat), CDI es una opción factible, y que gana peso con el paso del tiempo, para la definición de contextos y manejo de inyección de dependencias.
En este tutorial vamos a ver aspectos más avanzados que nos permiten trabajar con conceptos como Alternativas, Especialización, Productores, Eventos e Interceptores.
Para ayudarnos a retratar estos aspectos, vamos a realizar pequeñas aplicaciones web JSF en las que iremos utilizando las herramientas anteriormente detalladas.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: MacBook Pro 17′ (2.66 GHz Intel Core i7, 8GB DDR3 SDRAM).
- Sistema Operativo: Mac OS X Lion 10.10.3.
- NVIDIA GeForce GT 330M 512Mb.
- Crucial MX100 SSD 512 Gb.
-
Software:
- Eclipse Mars
- Java JDK 1.8
- Maven 3
- Apache Tomcat 8
- JBoss Weld 2.2.13
3. Alternativas
Mientras estamos desarrollando nuestra aplicación, puede darse el caso de que se tenga multiples implementaciones para un mismo bean, sobre todo si estamos acostumbrados a trabajar mediante diseño por contrato.
Para dar solución a estas situaciones CDI nos ofrece las Alternativas. Una herramienta aplicable en el momento de despliegue, que nos permite seleccionar entre las distintas implementaciones de una misma interfaz
Esta solución suele utilizarse en las siguientes situaciones:
- Manejar lógica de negocio específica de cada cliente en tiempo de ejecución.
- Seleccionar beans validos para escenarios concretos.
- Creación de dobles de test.
Para escenificar el uso de Alternativas vamos a utilizar como base el proyecto CDI del anterior tutorial Inyección de dependencias a través de CDI en Tomcat.
Aplicando las Alternativas.
Comenzaremos añadiendo una nueva implementación de la interfaz «Greeting», la cual utilizamos para implementar los servicios de la aplicación.
1 2 3 4 5 6 7 8 9 10 11 12 |
package cdi.webapp.advanced; import javax.enterprise.inject.Alternative; @Alternative public class AlternativeGreetingServiceImpl implements Greeting { public String getSalute() { return "Hi Dude!!!"; } } |
Mediante la anotación @Alternative marcamos la implementación como alternativa dentro de implementaciones existentes, pero no es el único paso para hacer uso de alternativas, siendo necesario declarar esta clase en la sección
1 2 3 4 5 6 7 8 |
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> <alternatives> <class>cdi.webapp.advanced.AlternativeGreetingServiceImpl</class> </alternatives> </beans> |
El resultado de nuestras modificaciones es que la alternativa de implementación prevalece sobre la opción «default».
Utilizando multiples Alternativas.
Podemos querer hacer uso de las múltiples alternativas que tengamos definidas, para estos casos disponemos de varias posibilidades, en forma de anotaciones:
- @Named
- @Qualifier
En este caso vamos a ampliar el supuesto anterior con otra implementación de la interfaz Greeting para tener un poco más de variedad en el ejemplo, para ello añadimos Alternative2GreetingServiceImpl, tanto al proyecto, como dentro del fichero beans.xml.
@Named
1 2 3 4 5 6 7 8 9 10 11 |
package cdi.webapp.advanced; import javax.enterprise.inject.Alternative; @Alternative public class Alternative2GreetingServiceImpl implements Greeting { public String getSalute() { return "Let's Rock!"; } } |
1 2 3 4 5 6 7 8 9 |
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> <alternatives> <class>cdi.webapp.advanced.AlternativeGreetingServiceImpl</class> <class>cdi.webapp.advanced.Alternative2GreetingServiceImpl</class> </alternatives> </beans> |
Llegados a este punto, se hace necesario identificar los distintos servicios que tenemos, en un primer momento realizaremos esta identificación a través de la anotación @Named. A continuación lo detallamos para cada implementación:
1 2 |
@Named("GreetingServiceImpl") public class GreetingServiceImpl implements Greeting... |
1 2 |
@Named("AlternativeGreetingServiceImpl") public class AlternativeGreetingServiceImpl implements Greeting... |
1 2 |
@Named("Alternative2GreetingServiceImpl") public class Alternative2GreetingServiceImpl implements Greeting ... |
En la clase MessageServerBean habrá que establecer dichos servicios…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
@Named @RequestScoped public class MessageServerBean { @Inject @Named("GreetingServiceImpl") private Greeting greetingService; @Inject @Named("AlternativeGreetingServiceImpl") private Greeting alternativeGreetingService; @Inject @Named("Alternative2GreetingServiceImpl") private Greeting alternative2GreetingService; public String getMessage(){ return greetingService.getSalute(); } public String getMessageAlternative(){ return alternativeGreetingService.getSalute(); } public String getMessageAlternative2(){ return alternative2GreetingService.getSalute(); } } |
Finalmente modificaremos el fichero xhtml para solicitar los distintos mensajes…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" > <h:head> <title>Facelet Title</title> </h:head> <h:body> Hello from Facelets <br/> <h:form> Message is: #{messageServerBean.message} <br/> Message Alternative is: #{messageServerBean.messageAlternative} <br/> Message Alternative 2 is: #{messageServerBean.messageAlternative2} <br/> Message Server Bean is: #{messageServerBean} </h:form> </h:body> </html> |
El resultado debe ser similar al que se muestra en la imagen a continuación:
@Qualifier
Si queremos optar por identificar nuestros servicios a través de @Qualifier tendremos que crearlos, como se ve a continuación:
1 2 3 4 5 |
@Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD}) public @interface GreetingService { } |
Sirva esta declaración a modo de ejemplo para realizar la definición del resto de @Qualifier (AlternativeGretingService y Alternative2GreetingService)
Después de definirlos, habrá que marcar cada servicio con su correspondiente @Qualifier sustituyendo la anotación @Named por @
Este cambio debe realizarse de igual manera en la clase MessageServerBean, quedando de la siguiente manera:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@Named @RequestScoped public class MessageServerBean { @Inject @GreetingService private Greeting greetingService; @Inject @AlternativeGreetingService private Greeting alternativeGreetingService; @Inject @Alternative2GreetingService private Greeting alternative2GreetingService; public String getMessage(){ return greetingService.getSalute(); } public String getMessageAlternative(){ return alternativeGreetingService.getSalute(); } public String getMessageAlternative2(){ return alternative2GreetingService.getSalute(); } } |
El resultado final debe ser similar al que mostramos en el punto anterior.
4. Especialización
La epecialización es una solución similar a las alternativas en lo que se refiere a sustituir un bean por otro. Sin embargo, lo realmente interesante de las especializaciones es que permite ampliar la funcionalidad de un bean específico sobreescribiendo sus métodos.
La especialización se realiza tanto en tiempo de ejecución como de compilación. Cuando se declara que un bean especializa otro, extenderá la clase, y en tiempo de ejcución el bean especializado sustituye por completo al bean inicial. Si el bean inicial se genera mediante un método «Productor», habrá que sobreescribir el método «Productor» parta que genere el bean especializado.
Declarar un bean especializado es sencillo, en nuestro caso vamos a especializar el bean Alternative2GreetingService, extendiendo la implementación y sobreescribiendo el método getSalute():
1 2 3 4 5 6 7 8 9 |
@Alternative @Specializes public class SpecializedGreetingService extends Alternative2GreetingServiceImpl{ @Override public String getSalute() { return "C'mon!!!! ".concat(super.getSalute()); } } |
Por otro lado, hace falta dejar constancia en el fichero beans.xml.
1 2 3 4 5 6 7 8 9 10 |
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> <alternatives> <class>cdi.webapp.advanced.AlternativeGreetingServiceImpl</class> <!-- class>cdi.webapp.advanced.Alternative2GreetingServiceImpl</class --> <class>cdi.webapp.advanced.specialization.SpecializedGreetingService</class> </alternatives> </beans> |
Con estos cambios el resultado que debe obtenerse es el siguiente:
5. Productores
Un método productor es aquel que genera un objeto que puede ser inyectado, tiene que estar anotado con javax.enterprise.inject.Produces.
En este caso vamos a volver al ejemplo base (Inyección de dependencias a través de CDI en Tomcat) para explicar como hacer uso de @Produces.
Primero ampliaremos los servicios de generación de saludo (AlternativeGreetingServiceImpl y Alternative2GretingServiceImpl) mediante las clases ya expuestas en este tutorial.
Con posterioridad, eliminaremos de estas (GreetingServiceImpl, AlternativeGreetingServiceImpl y Alternative2GreetingServiceImpl) las antoaciones que tienen.
A continuación generaremos la clase que contiene al método productor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class GreetingFactory implements Serializable { private final GreetingType greetingType = GreetingType.ALTERNATIVE_GREATING; @Produces @Greeting public IGreeting getGreeting(){ switch(greetingType){ case GREATING: return new GreetingService(); case ALTERNATIVE_GREATING: return new AlternativeGreetingService(); case ALTERNATIVE_2_GREATING: return new Alternative2GreetingService(); default: return new GreetingService(); } } } |
Como se ve en el código anterior, el método productor de Beans se marca con @Produces.Hemos mantenido el Qualifier @Greeting para hacer más cómodo el desarrollo.
Adicionalmente, hemos creado un enumerado que mantiene los tipos de servicios que mantenemos..
1 2 3 |
public enum GreetingType { GREATING, ALTERNATIVE_GREATING, ALTERNATIVE_2_GREATING } |
Finalmente, nos aseguramos de que el fichero beans.xml está vacío y de que el contenido del fichero index.xhtml es el correcto:
1 2 3 4 5 6 |
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> </beans> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Facelet Title</title> </h:head> <h:body> Hello from Facelets <br/> <h:form> Message is: #{messageServerBean.message} <br/> Message Server Bean is: #{messageServerBean} </h:form> </h:body> </html> |
Por último, el resultado:
6. Eventos
Los eventos son el mecanismo que ofrece CDI para que los beans se comuniquen «bajo demanda». Un bean puede definir un evento, otro puede «disparar» ese evento y un tercero puede ser notificado.
Un evento consiste en:
- Un objeto Java que define el evento
- Los qualifiers de eventos
Para este ejemplo vamos a hacer un proyecto nuevo por completo. Como ya hemos visto con anterioridad como configurar de base un proyecto CDI con tomcat, saltaremos a la parte de la definición de objetos.
Primero definimos el evento
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class HelloEvent { private final String message; public HelloEvent(final String message) { super(); this.message = message; } public String getMessage() { return message; } } |
A continuación creamos un listener que esté observando por si el evento se dispara:
1 2 3 4 5 6 7 |
@Stateless public class HelloListener { public void listenToHello(@Observes final HelloEvent helloEvent){ System.out.println("HelloEvent: " + helloEvent.getMessage()); } } |
El Listener esta anotado con @Stateless que marca el bean como stateless, y por otro lado, vemos que el parámetro del método listenToHello esta anotado con @Observes, lo que quiere decir que se trata de un parametro del evento, y que hace que el método listenToHello sea declarado como método observer.
En tercer lugar tenémos la clase HelloMessenger, encargada de disparar el evento, junto con su parámetro:
1 2 3 4 5 6 7 8 9 10 |
@Named("messenger") @Stateless public class HelloMessenger { @Inject Event<HelloEvent> events; public void hello() { events.fire(new HelloEvent("from bean " + System.currentTimeMillis())); } } |
Finalmente definimos una página web (index.xhtml) donde estableceremos le código que hará la llamada al método hello(), encargado de disparar el evento.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" > <h:head> <title>Facelet Title</title> </h:head> <h:body> <h:form> <h:commandButton value="Fire!" action="#{messenger.hello}"/> </h:form> </h:body> </html> |
Al arrancar la aplicación se verá una página web con un botón (Fire).
Si el botón es pulsado lanzará el evento, que será recogido por el Listener y se encargará de pintar el mensaje indicado por consola.
7. Conclusiones
Como hemos visto a lo largo del tutorial, CDI nos provee de las herramientas necesarias para enfrentarnos a casi cualquier proyeto en el que se necesite un contexto y que requiera de inyección de dependencias.
Con respecto al futuro de CDI, pasa por su especificación 2.0, con la llegada de JEE 8, que se centrará en dos aspectos principalmente:
- Soporte CDI para aplicaciones Java SE puras.
- Modularidad. Definiendo un modelo CDI con mayor granularidad favoreciendo la integración de otras especificaciones Java EE