CDI: Conceptos avanzados

0
10978

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

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.

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 del fichero beans.xml, como se puede ver a continuación.


  
  	cdi.webapp.advanced.AlternativeGreetingServiceImpl
  

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

package cdi.webapp.advanced;

import javax.enterprise.inject.Alternative;

@Alternative
public class Alternative2GreetingServiceImpl implements Greeting {

	public String getSalute() {
		return "Let's Rock!";
	}
}

      
      	cdi.webapp.advanced.AlternativeGreetingServiceImpl
      	cdi.webapp.advanced.Alternative2GreetingServiceImpl
      

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:

@Named("GreetingServiceImpl")
public class GreetingServiceImpl implements Greeting...
@Named("AlternativeGreetingServiceImpl")
public class AlternativeGreetingServiceImpl implements Greeting...
@Named("Alternative2GreetingServiceImpl")
public class Alternative2GreetingServiceImpl implements Greeting ...

En la clase MessageServerBean habrá que establecer dichos servicios…

@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…


	
		Facelet Title
	
	
	    Hello from Facelets
	    
Message is: #{messageServerBean.message}
Message Alternative is: #{messageServerBean.messageAlternative}
Message Alternative 2 is: #{messageServerBean.messageAlternative2}
Message Server Bean is: #{messageServerBean}

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:

@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 @, por ejemplo, se debe sustituir @Named(«GreetingServiceImpl») por @GreetingService.

Este cambio debe realizarse de igual manera en la clase MessageServerBean, quedando de la siguiente manera:

@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():

@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.


  
  	cdi.webapp.advanced.AlternativeGreetingServiceImpl
  	
  	cdi.webapp.advanced.specialization.SpecializedGreetingService
  

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:

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..

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:







	
		Facelet Title
	
	
	    Hello from Facelets
	    
Message is: #{messageServerBean.message}
Message Server Bean is: #{messageServerBean}

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

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:

@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:

@Named("messenger")
@Stateless
public class HelloMessenger {
	
	@Inject Event 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.




	
		Facelet Title
	
	 

        

            

        
	    
	

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

8. Referencias

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad