Introducción a CDI Beans 2.0 (JEE 8)

logo java

JEE (Java Enterprise Edition) es una colección de especificaciones especialmente orientadas al desarrollo de aplicaciones empresariales generalmente del lado del servidor (aplicaciones web). Incluye tecnologías tales como Servlet, JSP, EJB, JAX-WS, JPA o JMS. La versión más actual en el momento de escribir el presente tutorial es la 8 (su versionado no coincide con el de Java SE que es el core del propio lenguaje Java).

El desarrollo de JEE es controlado por Oracle y realizado dentro del Java Community Process(JCP), pero a finales de 2017 Oracle cedió JEE a la fundación Eclipse y, por cuestiones legales en el uso de la marca Java EE, ha pasado a llamarse Jakarta EE. La primera versión es Jakarta EE 8 y coincide exactamente con las especificaciones Java EE 8. Los cambios llegarán con Jakarta EE 9, prevista para finales de 2020 y basada en Java 11.

Una especificación sólo describe las funcionalidades de una API, por lo que en cada caso tenemos que elegir una librería que la implemente y que generalmente suelen ofrecer funcionalidades adicionales no contempladas en la especificación. Por ejemplo, JPA es implementada por Hibernate, OpenJPA y EclipseLink, entre otros productos. Un servidor de aplicaciones JEE es un entorno que ya incluye e integra todas las implementaciones necesarias para cumplir con el conjunto de especificaciones definidas por cierta versión de JEE. Por tanto, en teoría podemos utilizar un servidor que implemente la especificación JEE 8 para ejecutar cualquier aplicación 100 % compatible con la misma -otro asunto son las configuraciones a realizar en el servidor-.

La especificación CDI Beans – (CDI: Context Dependency Injection), define el framework de inyección de dependencias utilizado de forma estándar en la plataforma Java EE siendo un elemento fundamental de la misma. Su funcionamiento y cometido es equivalente al sistema de inyección de dependencias del framework Spring. De una u otra forma haremos uso de esta especificación cualquier aplicación JEE así que resulta fundamental tener unos conocimientos básicos de la misma, y precisamente ese es el objetivo del presente tutorial en el que usaremos la versión CDI Beans 2.0 que es la incluida en JEE 8.

Proyecto para pruebas

Para realizar pruebas, voy a utilizar una aplicación web muy simple basada en JEE 8 que ofrece un servicio REST. Esta aplicación consistirá en un proyecto Maven estándar que se empaquetará en un .war y que tiene el siguiente pom.xml.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<packaging>war</packaging>

	<groupId>com.danielme</groupId>
	<artifactId>cdibeans</artifactId>
	<version>1.0</version>
	<description>A simple Rest API that uses JEE 8</description>

	<build>
		<finalName>cdibeans</finalName>
	</build>

	<properties>
		<failOnMissingWebXml>false</failOnMissingWebXml>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-api</artifactId>
			<version>8.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.22</version>
			<scope>provided</scope>
		</dependency>

	</dependencies>

</project>

Para hacer uso de todas las especificaciones recogidas por JEE 8 sólo he tenido que definir una única dependencia y además de tipo provided ya que el servidor de aplicaciones que voy a utilizar, en mi caso Wildfly 18 de Red Hat, proporciona todas las librerías necesarias. En el caso de CDI Beans, se incluye WELD, la implementación de referencia. Adicionalmente, utilizaré slf4j como proveedor del sistema de log que también es incluido en Wildfly 18.

Para utilizar CDI Beans es necesario crear el fichero de configuración /src/main/webapp/WEB-INF/beans.xml.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
      http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
    bean-discovery-mode="all">

</beans>

“Activamos” el uso de JAX-RS, disponible en Wildfly gracias a la librería RESTEasy, con la siguiente clase.

package com.danielme.cdibeans.rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("api")
public class JAXActivator extends Application {
}

Cursos de programación

y este es el servicio REST

package com.danielme.cdibeans.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/demo")
public class DemoResource {

	private static final Logger logger = LoggerFactory.getLogger(DemoResource.class);

	@GET
	public void getDemo() {
		logger.info("hello!!");
	}

}

Así de simple es crear un servicio REST con JEE 8. Si lo desplegamos, responderá en la url http://localhost:8080/cdibeans/api/demo con un 204. En mi caso utilizo Wildfly 18.0.1.Final y el despliegue se puede hacer desde Eclipse utilizando el plugin de JBoss o bien manualmente creando el artefacto cdibeans.war con el comando mvn clean package y copiándolo al directorio
/wildfly-18.0.1.Final/standalone/deployments/. Wildfly se lanza con el script /wildfly-18.0.1.Final/bin/standalone.bat en Windows y /wildfly-18.0.1.Final/bin/standalone.sh en Linux. Obsérvese que nuestra aplicación sólo pesa 12kb porque todas las librerías que utiliza ya están en el servidor.

Primeros pasos

El ciclo de vida de los CDI beans, esto es, los objetos o instancias de clases que son creados y gestionados por la implementación de CDI Beans, viene determinado por su ámbito de existencia denominado en inglés como scope. Este scope se define mediante anotaciones, veamos las dos más habituales.

  • @Dependent. Cada vez que se solicita una instancia de la clase se crea una nueva. Es el scope por defecto, y esto es una diferencia muy importante con respecto a Spring ya que en este último por defecto siempre se devuelve la misma instancia de la clase (el scope singleton)

  • @ApplicationScoped. Se crea una única instancia de la clase y siempre se inyecta esa misma. El bean se comporta por tanto como un singleton. Es el scope que usaremos habitualmente para clases de servicios, daos, controladores de servicios web y similares que no tienen estado.

Veamos ambos casos con un ejemplo. La siguiente clase simplemente tiene un método que devuelve una cadena.

package com.danielme.cdibeans.services;

public class StringService {

	public String print() {
		return "Hello";
	}

}

Hagamos que el servicio REST llame al método print para mostrar el resultado en el log. Indicamos a CDI Beans que proporcione a la clase DemoResource una instancia de la clase StringService utilizando la anotación @Inject.

package com.danielme.cdibeans.rest;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.danielme.cdibeans.services.StringService;

@Path("/demo")
public class DemoResource {

	private static final Logger logger = LoggerFactory.getLogger(DemoResource.class);

	@Inject
	private StringService stringService;

	@GET
	public void getDemo() {
		logger.info(this.toString());
		logger.info(stringService.toString());
	}

}

En el ejemplo se ha realizado la inyección de la dependencia directamente en un atributo de la clase, más adelante veremos otras opciones.


Si se realizan sucesivas llamadas veremos en el log que cada vez se utiliza una instancia distinta de las clases DemoResource y StringService. Estas instancias han sido creadas por el servidor.

INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@1001647d
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@11942b56
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@d289fd
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@2dc2d09b
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@3470670b
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@3799a283

Si anotásemos las dos clases con @Dependent el resultado sería el mismo pues es el comportamiento por defecto.

En ambos casos, no tiene ningún sentido crear un nuevo objeto cada vez que lo necesitemos y podemos utilizar siempre el mismo, así que procedemos a anotar ambas clases con @ApplicationScoped.

package com.danielme.cdibeans.services;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class StringService {

	public String print() {
		return "Hello";
	}

}
package com.danielme.cdibeans.rest;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.danielme.cdibeans.services.StringService;

@ApplicationScoped
@Path("/demo")
public class DemoResource {

	private static final Logger logger = LoggerFactory.getLogger(DemoResource.class);

	@Inject
	private StringService stringService;

	@GET
	public void getDemo() {
		logger.info(this.toString());
		logger.info(stringService.toString());
	}

}

Repitiendo la prueba veremos lo siguiente:

INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@42c19e4a
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@1d5a1711
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@42c19e4a
INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@1d5a1711

Otros scopes disponibles son los siguientes.

  • @RequestScope: los beans creados con este scope existen todo el ciclo de ejecución de una petición HTTP, y en ese ciclo siempre se inyectará la misma instancia.
  • @SessionScope: los beans en este scope están asociados a una sesión HTTP. Esto es fácil de ver en nuestro ejemplo asignando  este scope a StringService y haciendo la clase Serializable (todos los beans que sean vinculados directa o indirectamente a una sesión deben ser serializables).
    package com.danielme.cdibeans.services;
    
    import java.io.Serializable;
    
    import javax.enterprise.context.SessionScoped;
    
    @SessionScoped
    public class StringService implements Serializable {
    	
    	private static final long serialVersionUID = -870359121196178116L;
    
    	public String print() {
    		return "Hello";
    	}
    
    }

    Recuperamos el objeto HttpSession en el servicio REST.

    @GET
    public void getDemo(@Context HttpServletRequest request) {
        logger.info("session [{}]", request.getSession().getId());
        logger.info(this.toString());
        logger.info(stringService.toString());
    }
    

    Si llamamos al servicio REST desde navegadores distintos veremos que se utiliza la misma instancia de StringService para todas las llamadas realizadas desde un mismo navegador al ser la sesión siempre la misma. Y, en todos los casos, la instancia de DemoResource es la misma.

    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) session [8XGe3YutR4FqQqmAoJplJwvVgEM7Fo38ltvr7KOm]
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@78f18743
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@210c2b92
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) session [pLusUUasM8hywQgJAH2b96R5Qogq0tcm5vPQBxNf]
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@78f18743
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@76454c22
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) session [8XGe3YutR4FqQqmAoJplJwvVgEM7Fo38ltvr7KOm]
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.DemoResource@78f18743
    INFO  [com.danielme.cdibeans.DemoResource] (default task-1) com.danielme.cdibeans.beans.StringService@210c2b92
    

La especificación incluye scopes más avanzados que quedan fuera del alcance del presente tutorial tales como ConversationScoped, TransactionScoped, FlowScoped y ViewScoped.

Inyección de interfaces

Hemos visto cómo inyectar una instancia de una clase, pero también podemos realizar inyecciones de interfaces para desacoplar la clase en la que realizamos la inyección de la implementación de dicha interfaz. Esta praxis es fundamental para aumentar el nivel de abstracción de nuestro código y seguir las buenas prácticas de los principios SOLID o de clean code.

Veamos cómo funciona este mecanismo utilizándolo directamente en nuestro servicio REST creando la siguiente interfaz.

package com.danielme.cdibeans.services;

public interface ContractService {

    void foo();
}

Y una implementación

package com.danielme.cdibeans.services;

public class ContractServiceImpl implements ContractService {

    @Override
    public void foo() {
    }

}

Vamos a inyectarla en DemoResource del siguiente modo.

    @Inject
	private StringService stringService;
	@Inject
	private ContractService contractService;

	@GET
	public void getDemo(@Context HttpServletRequest request) {
		logger.info(contractService.getClass().getSimpleName());
		logger.info("session [{}]", request.getSession().getId());
		logger.info(this.toString());
		logger.info(stringService.toString());
	}

Si desplegamos y llamamos a la url veremos en el log que se ha inyectado una instancia de la clase ContractServiceImpl porque Weld ha encontrado una clase que implementa la interfaz que queríamos inyectar.

INFO  [com.danielme.cdibeans.DemoResource] (default task-1) ContractServiceImpl

Ahora, definamos otra implementación de ContractService.

package com.danielme.cdibeans.services;

public class ContractServiceImpl2 implements ContractService {

	@Override
	public void foo() {
		// do nothing
	}

}

La aplicación no arrancará porque Weld no sabe qué implementación de ContractService debe inyectar y en el log del servidor veremos un error parecido al siguiente.

org.jboss.weld.exceptions.DeploymentException: WELD-001409: Ambiguous dependencies for type ContractService with qualifiers @Default
  at injection point [BackedAnnotatedField] @Inject private com.danielme.cdibeans.DemoResource.contractService
  at com.danielme.cdibeans.DemoResource.contractService(DemoResource.java:0)
  Possible dependencies: 
  - Managed Bean [class com.danielme.cdibeans.beans.ContractServiceImpl] with qualifiers [@Any @Default],
- Managed Bean [class com.danielme.cdibeans.beans.ContractServiceImpl2] with qualifiers [@Any @Default]

Si utilizamos Eclipse con el plugin de JBoss veremos el siguiente warning en el editor de la clase.

Lo que haremos es darle un nombre distinto a cada implementación utilizando la anotación @Named

@Named("ContractServiceImpl")
public class ContractServiceImpl implements ContractService {
@Named("ContractServiceImpl2")
public class ContractServiceImpl2 implements ContractService {

…y utilizar esa misma anotación para indicarle a @Inject el bean que queremos.

@Inject
@Named("ContractServiceImpl2")
private ContractService contractService;

También es posible inyectar una instancia de cada una de las implementaciones disponibles con la anotación @Any.

@Inject
@Any
Instance<ContractService> contracts;	

@GET
public void getDemo(@Context HttpServletRequest request) {
	contracts.forEach(c->logger.info(c.getClass().getSimpleName()));

El uso de @Named nos obliga a utilizar cadenas para nombrar a los beans lo que resulta “feo” y puede causar problemas al practicarse refactorizaciones. Existe una alternativa más elegante pero que requiere escribir un poco de código consistente en crear anotaciones de tipo @Qualifier para identificar unívocamente cada implementación. Por ejemplo, vamos a crear otra interfaz.

package com.danielme.cdibeans.services;

public interface QualifierService {

	void fake();

}

Y dos implementaciones

package com.danielme.cdibeans.services;

public class QualifierServiceImpl implements QualifierService {

	@Override
	public void fake() {
	}

}
package com.danielme.cdibeans.services;

public class QualifierServiceImpl2 implements QualifierService {

	@Override
	public void fake() {
	}

}

Para cada implementación creamos una anotación.

package com.danielme.cdibeans.services;

import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ FIELD, METHOD, TYPE, PARAMETER })
public @interface QualifierService1 {
}
package com.danielme.cdibeans.services;

import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ FIELD, METHOD, TYPE, PARAMETER })
public @interface QualifierService2 {
}

Las aplicamos a la implementación correspondiente.

package com.danielme.cdibeans.services;

@QualifierService1
public class QualifierServiceImpl implements QualifierService {

	@Override
	public void fake() {
	}

}
package com.danielme.cdibeans.services;

@QualifierService2
public class QualifierServiceImpl2 implements QualifierService {

	@Override
	public void fake() {
	}

}

Para realizar la inyección usamos estas nuevas anotaciones en lugar de @Named.

@Inject
@QualifierService1
QualifierService qualifierService;

Singleton y los pseudoscope

Hemos visto que el scope @ApplicationScoped crea una única instancia del bean para inyectarla durante todo el tiempo de ejecución de la aplicación. Sin embargo, la especificación  incluye la anotación javax.inject.singleton (no confundir con la anotación javax.ejb.singleton que es para ejb) que permite definir, tal y como su nombre indica, un bean del que sólo tendremos una única instancia en la aplicación. ¿Cuál es la diferencia entre ambas anotaciones? Singleton no es un scope al uso como ApplicationScoped, sino uno de los dos pseudo-scope definidos por CDI Beans (el otro es @Dependent). Desde el punto de vista del código, un pseudoscope es un scope anotado con @Scope y no con @NormalScope.

Al instanciarse un bean de tipo @NormalScope, CDI Beans no devuelve una referencia directa al propio bean en sí sino un objeto proxy del mismo lo que permite, entre otros, gestionar su serialización y en el caso de los beans de sesión que vimos anteriormente realizar la “magia” para inyectar la instancia del bean asociada a la sesión web del cliente que hace la petición.

Como regla general, usaremos siempre que sea posible @ApplicationScoped. Uno de los casos en los que no es posible es el de las clases finales. Si por ejemplo declaramos como final la clase StringService que hemos definido con SessionScope veremos el siguiente error.

Caused by: org.jboss.weld.exceptions.UnproxyableResolutionException: WELD-001437: Bean type class com.danielme.cdibeans.beans.StringService is not proxyable because it is final - Managed Bean [class com.danielme.cdibeans.beans.StringService] with qualifiers [@Any @Default].

Modos de inyección

Hasta ahora hemos realizado las inyecciones de beans directamente en atributos de la clase -la práctica más habitual-, pero tenemos otras opciones.

  • Hacer la inyección mediante la invocación de un método de tipo set. Por ejemplo:
        @Inject
        public void setDemoService(StringService stringService) {
            this.stringService = stringService;
        }
    
  • Definir un constructor que reciba todos los beans a inyectar. Podemos crear todos los constructores que queramos pero sólo uno puede estar anotado con @Inject.

Vamos a realizar la inyección de todas las dependencias utilizando un constructor en una copia de la versión actual de la clase DemoResource que llamaremos DemoResource2. Quedaría tal que así.

package com.danielme.cdibeans.rest;

import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.danielme.cdibeans.services.ContractService;
import com.danielme.cdibeans.services.StringService;

@ApplicationScoped
@Path("/demo2")
public class DemoResource2 {

	private static final Logger logger = LoggerFactory.getLogger(DemoResource2.class);

	private final StringService stringService;
	private final ContractService contractService;
	private final List<String> stringList;	
	
	@Inject
	public DemoResource2(StringService stringService, @Named("ContractServiceImpl2") ContractService contractService, List<String> stringList) {
		super();
		this.stringService = stringService;
		this.contractService = contractService;
		this.stringList = stringList;
	}


	@GET
	public void getDemo(@Context HttpServletRequest request) {
		stringList.forEach(logger::info);
		logger.info(contractService.getClass().getSimpleName());
		logger.info("session [{}]", request.getSession().getId());
		logger.info(this.toString());
		logger.info(stringService.toString());
	}

}

Pero si desplegamos la aplicación se producirá el siguiente error.

Caused by: java.lang.RuntimeException: RESTEASY003190: Could not find constructor for class: com.danielme.cdibeans.DemoResource2
	at org.jboss.resteasy.spi.metadata.ResourceBuilder.getConstructor(ResourceBuilder.java:805)
	at org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.registered(POJOResourceFactory.java:56)

Hemos usado un mal ejemplo: los beans de JAX-RS requieren la existencia del constructor sin argumentos para poder crear un proxy para la clase (la propia especificación define este requisito). Eso sí, para crear las instancias “reales” accesibles desde el proxy se utiliza el constructor con parámetros marcado con @Inject. Por tanto, la clase deberá quedar como sigue.

@ApplicationScoped
@Path("/demo2")
public class DemoResource2 {

	private static final Logger logger = LoggerFactory.getLogger(DemoResource2.class);

	private StringService stringService;
	private ContractService contractService;
	private List<String> stringList;

	public DemoResource2() {
		//use for proxy creation
	}

	@Inject
	public DemoResource2(StringService stringService, @Named("ContractServiceImpl2") ContractService contractService,
			List<String> stringList) {
		super();
		this.stringService = stringService;
		this.contractService = contractService;
		this.stringList = stringList;
	}

Nota: las tres formas de hacer la inyección se pueden utilizar en una misma clase.

@PostConstruct y @PreDestroy

Para realizar tareas de configuración de los CDI beans no es necesario definir constructores sino anotar con @PostConstruct un método de la propia clase. Este método se ejecuta justo después de haberse creado el objeto y en ese momento ya se ha realizado la inyección de dependencias.

  @PostConstruct
    public void postConstruct() {
        logger.info("postConstruct, beans injected ");
    }

Asimismo, la anotación @PreDestroy permite ejecutar un método justo antes de que el objeto sea destruido en Weld, por ejemplo si el bean es de tipo @ApplicationScoped cuando la aplicación se detenga o si el bean es de tipo @RequestScoped al finalizar el request al que el objeto se encuentra asociado. 

    @PreDestroy
    void preDestroy() {
        logger.info("@PreDestroy");
    }

Factorías

Si bien con @PostConstruct podemos configurar los objetos a medida que van siendo creados por Weld,  también tenemos la posibilidad de instanciar nosotros mismos los beans que formarán parte del motor CDI. Esto permite por ejemplo:

  • Crear beans cuya implementación puede cambiar en tiempo de ejecución.
  • Invocar constructores con parámetros.
  • Integrar clases que no están en nuestra aplicación y no podemos modificar o extender por ejemplo para añadir un método @PostConstruct.

Para instanciar manualmente un bean inyectable por CDI Beans tenemos que crear un método “factoría” que lo devuelva y anotarlo con @Produces. Opcionalmente indicaremos también el scope (si no se aplicará el valor por defecto que recordemos es @Dependent) y podemos añadir la anotación @Named si fuera necesario darle un nombre al bean. Por ejemplo vamos a crear un listado de cadenas único para toda la aplicación.

package com.danielme.cdibeans.services;

import java.util.Arrays;
import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Named;

public class DemoProducer {

	@Produces
	@ApplicationScoped
	List<String> produceStrings() {
		return Arrays.asList("string 1", "string 2");
	}

}

Y lo inyectamos en nuestro servicio REST siguiendo la praxis habitual.

@Inject
List<String> stringList;

@GET
public void getDemo(@Context HttpServletRequest request) {
   stringList.forEach(logger::info);
}

Si en un método “productor” necesitamos recibir algunos beans podemos inyectarlos añadiéndolos como parámetros al método. Si fuera necesario se pueden anotar con @Named.

 @Produces
 @Named("className")
 String produceClassName(@Named("ContractServiceImpl") ContractService contractService) {
    return contractService.getClass().getSimpleName();
 }

Para los beans instanciados con @Produces podemos definir un método “destructor” que se ejecutará cuando los objetos ya no sean necesarios al haber finalizado el ciclo de vida determinado por su scope. Este método debe recibir como primer parámetro el objeto, anotado con @Disposes, y a continuación y de forma opcional los beans que necesitemos recibir en el método. Un ejemplo típico puede ser cerrar una conexión de base de datos.

void close(@Disposes Connection connection) {
try {
    connection.close(); }
catch (SQLException ex) {
    logger.w(ex);
}

Interceptores

Una funcionalidad muy interesante de CDI Beans es la posibilidad de implementar una función que se ejecute de forma automática siempre que se invoquen ciertos métodos. Estas funciones se denominan interceptores y conceptualmente les resultarán más que familiares a aquellos programadores que hayan trabajado con aspectos o Struts 2.

En la práctica un interceptor no es más que un método anotado con @AroundInvoke. Este método lo definimos en una clase propia si el interceptor se va a invocar desde varias clases, o directamente dentro de la clase que contiene los métodos a interceptar.

package com.danielme.cdibeans.interceptors;

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogCallInterceptor {

	private static final Logger logger = LoggerFactory.getLogger(LogCallInterceptor.class);

	@AroundInvoke
	public Object log(InvocationContext context) throws Exception {
		logger.info("method {} called", context.getMethod().toString());
		return context.proceed();
	}

}

En el método @AroundInvoke, que no puede ser ni estático ni final, recibimos un objeto InvocationContext que modela los detalles de la llamada al método que estamos interceptando. Este método debe llamar a context.proceed() para continuar con la invocación al método original, y puede lanzar cualquier excepción. En el ejemplo, el interceptor imprime en el log el método cuya invocación ha lanzado la ejecución del interceptor.

El interceptor se puede aplicar a cualquier clase gestionada por CDI Beans utilizando la anotación @Interceptors a nivel de clase o de forma específica para un método en concreto. Se ejecutarán todos los interceptores indicados en la anotación respetando el orden establecido.

@ApplicationScoped
@Path("/demo")
@Interceptors({ LogCallInterceptor.class })
public class DemoResource {

Código de ejemplo

El código de ejemplo del tutorial completo se encuentra en GitHub. Para más información sobre cómo utilizar GitHub consultar este artículo.

Master Pyhton, Java, Scala or Ruby

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios .