Introducción a CDI Beans 2.0 (JEE 8)

Última actualización: 30/07/2021

logo java

JEE (Java Enterprise Edition) es una colección de especificaciones o estándares oficiales orientadas al desarrollo de aplicaciones empresariales, generalmente del lado del servidor (aplicaciones web). Incluye tecnologías tales como Servlet, JSP, EJB, JPA o JMS. La versión más actual (y la última bajo el nombre JEE) en el momento de escribir el presente tutorial es la 8 (su versionado no coincide con el del lenguaje Java).

Importante. Con posterioridad a la publicación de este tutorial, he creado un completo curso dedicado a Jakarta EE que incluye un bloque de seis capítulos que analiza CDI. Recomiendo su consulta en lugar del presente artículo.

CDI 1 – Inyección de dependencias
CDI 2 – Ámbitos (scopes)
CDI 3 – Calificadores (qualifiers)
CDI 4 – Productores
CDI 5 – Interceptores y decoradores
CDI 6 – Eventos

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, JEE ha pasado a llamarse Jakarta EE. La primera versión lanzada por Eclipse fue Jakarta EE 8 y coincide con JEE 8. Los cambios llegaron con Jakarta EE 9, pero no a nivel funcional: se ha modificado el nombre de los paquetes javax.* a jakartaee.*, y unas pocas especificaciones han pasado a ser opcionales o directamente han dejado de ser parte de Jakarta EE 9. En cualquier caso, todo lo visto en este tutorial sigue siendo válido para Jakarta EE 8 y 9.

Una especificación solo describe las funcionalidades de una API, por lo que en cada caso tenemos que elegir una librería que la implemente. Por ejemplo, en JPA contamos con Hibernate, OpenJPA y EclipseLink, entre otros productos. Un servidor de aplicaciones JEE es un entorno de ejecución que 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 JEE 8 para ejecutar cualquier aplicación 100 % compatible con la misma -otro asunto son las configuraciones a realizar en el servidor-.

Para más información sobre el paso de JEE a Jakarta EE y el mundo de las especificaciones en Java, consultar Curso Jakarta EE (1): Introducción.

La especificación CDI Beans – (CDI: Contexts and Dependency Injection), define el marco de trabajo 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 de Spring. De una u otra forma, haremos uso de esta especificación en cualquier aplicación JEE, así que resulta fundamental tener unos conocimientos básicos.

Proyecto para pruebas

Con el fin de probar los conceptos que vayamos viendo, usaré una aplicación web muy simple basada en JEE 8 y Java 11 que ofrece un servicio REST. Consiste en un proyecto Maven estándar que se empaquetará en un .war y que cuenta con 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>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<failOnMissingWebXml>false</failOnMissingWebXml>
		<maven.compiler.source>11</maven.compiler.source>
		<maven.compiler.target>11</maven.compiler.target>
	</properties>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.wildfly.bom</groupId>
				<artifactId>wildfly-jakartaee8</artifactId>
				<scope>import</scope>
				<type>pom</type>
				<version>24.0.1.Final</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<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>
			<scope>provided</scope>
		</dependency>

	</dependencies>

</project>

Para hacer uso de todas las especificaciones recogidas por JEE 8 solo he tenido que definir una única dependencia y además de tipo provided ya que el servidor de aplicaciones que voy a utilizar, WildFly 24 de Red Hat \ JBoss, proporciona todas las librerías necesarias por ser compatible con JEE 8. En el caso de CDI, se incluye WELD.

Adicionalmente, utilizaré slf4j como proveedor del sistema de bitácora, pues WildFly lo incluye para facilitarnos el trabajo. Para declarar la versión exacta contenida en el servidor, he usado una dependencia especial de tipo BOM que define las versiones de las librerías empaquetadas por cierta versión de WildFly.

Hagamos que todas las clases de la aplicación sean utilizables por CDI (práctica habitual). Creamos el fichero de configuración opcional /src/main/webapp/WEB-INF/beans.xml y establecemos la propiedad bean-discovery-mode con el valor all.

<?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_2_0.xsd"
       bean-discovery-mode="all" version="2.0">

</beans>

Habilitamos el uso de JAX-RS, la especificación para trabajar con servicios REST, 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 {
}

Este es el servicio REST, una clase anotada con @Path en la que los “endpoints” se implementan en métodos anotados con el verbo de las llamadas HTTP que procesa cada uno (@GET, @POST, etc).

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 String getDemo() {
		logger.info("hello!!");
                return "hello";
	}

}

Así de simple es crear un servicio REST con JEE 8, no entraré en más detalles porque no es el tema de este tutorial. Si lo desplegamos, responderá en la url http://localhost:8080/cdibeans/api/demo con el texto. En mi caso utilizo WildFly 24.0.1.Final y el despliegue se puede hacer desde Eclipse mediante el plugin de JBoss o bien a mano 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-24.0.1.Final/bin/standalone.bat en Windows y /wildfly-24.0.1.Final/bin/standalone.sh en Linux. Obsérvese que nuestra aplicación solo pesa 12kb porque todo lo que necesitamos (las APIs de JEE 8 y sus implementaciones) está en el servidor.

El proceso de creación de un proyecto Jakarta EE \ JEE con Maven y su despliegue de forma manual y con los IDE Eclipse e IntelliJ Ultimate lo explico aquí.

Primeros pasos

Los objetos o instancias de clases que son creados y gestionados por la implementación de CDI existen dentro de un componente conocido como el contenedor de CDI y se denominan CDI beans. Estos objetos tienen un ciclo de vida determinado por su ámbito de existencia (scope). El ámbito 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 ámbito por omisión, y esto es una diferencia muy importante con respecto a Spring, ya que en este último de forma predeterminada siempre se devuelve la misma instancia de la clase.

  • @ApplicationScoped. Se crea una única instancia y siempre se inyecta esa misma. El objeto se comporta, por tanto, como un singleton. Es el ámbito habitual de las clases con servicios, daos, controladores de servicios web y similares que no tienen estado.

Veamos ambos casos con un ejemplo. La siguiente clase contiene 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 a print para mostrar el resultado en el log. Indicamos al contenedor de CDI que proporcione (inyecte) a la clase DemoResource una instancia de 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 String getDemo() {
		logger.info(this.toString());
		logger.info(stringService.toString());
                return "hello";
	}

}

Este es un ejemplo muy básico del patrón de diseño “inyección de dependencias” sobre el que se fundamenta el estándar CDI. Es la técnica con la que se construyen las aplicaciones JEE.


Si se realizan sucesivas llamadas veremos en la bitácora del servidor que cada vez se utiliza una instancia distinta de las clases DemoResource y StringService.

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

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 recurrimos a la anotación @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 String getDemo() {
		logger.info(this.toString());
		logger.info(stringService.toString());
        return "hello"; 
	}

}

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 ámbitos disponibles y orientados a las aplicaciones web son los siguientes.

  • @RequestScope: los objetos creados con este ámbito solo existen mientras lo haga la petición HTTP a la que están asociados.
  • @SessionScope: estos objetos están asociados a una sesión HTTP. Esto es fácil de ver en nuestro ejemplo asignando este ámbito a StringService y haciendo la clase Serializable (todos los beans que sean vinculados directa o indirectamente a una sesión deben serlo).
    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 porque la sesión siempre es la misma. Y, en todos los casos, la instancia de DemoResource coincide por ser de ámbito @ApplicationScope.

    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 otros ámbitos más avanzados de uso menos habitual 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 recibimos la inyección de las implementaciones 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 código limpio. El objetivo es conseguir que nuestras aplicaciones sean modulares y fáciles de evolucionar.

Veamos cómo funciona este mecanismo utilizándolo en nuestro servicio REST. Creamos una nueva 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 String getDemo(@Context HttpServletRequest request) {
		logger.info(contractService.getClass().getSimpleName());
		logger.info("session [{}]", request.getSession().getId());
		logger.info(this.toString());
		logger.info(stringService.toString());
                return "hello";
	}

Si desplegamos y llamamos a la url, podemos comprobar en la bitácora que se inyectó un objeto 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 tiene que resolver todas las dependencias (no son opcionales). 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, aparecerá este aviso 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 {

Usaremos esa misma anotación para indicarle a @Inject el objeto que queremos.

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

Se puede inyectar una instancia de cada una de las implementaciones disponibles con @Any.

@Inject
@Any
private Instance<ContractService> contracts;	

@GET
public String 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 a las clases. 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 un “calificador personalizado”, que no es más que 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 {
}

Usamos estos calificadores para identificar de forma unívoca a cada implementación.

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
private QualifierService qualifierService;

Singleton y los pseudoscope

Hemos comprobado que el ámbito @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 perteneciente a EJB) que permite definir, tal y como su nombre indica, una clase de la que solo tendremos una única instancia en la aplicación. ¿Cuál es la diferencia entre ambas anotaciones? Singleton no es un ámbito al uso como ApplicationScoped, sino uno de los dos pseudo-scope definidos por CDI (el otro es @Dependent). Desde el punto de vista del código, un pseudoscope es un ámbito anotado con @Scope y no con @NormalScope.

Al instanciarse un bean de tipo @NormalScope, el contenedor no devuelve una referencia directa al objeto creado sino un objeto representante (proxy) del mismo. Esto permite, entre otros, gestionar su serialización y, en el caso de los beans de sesión, realizar la “magia” para inyectar siempre la instancia asociada a la sesión web del cliente que hace la petición.

Como regla general, usaremos siempre @ApplicationScoped en lugar de @Singleton.

Puntos de inyección

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

  • Hacer la inyección en un método, por ejemplo un setter.
        @Inject
        public void setDemoService(StringService stringService) {
            this.stringService = stringService;
        }
    
  • Definir un constructor que reciba todas las dependencias. Podemos crear los constructores que necesitemos, pero solo uno puede estar marcado con @Inject. Es la opción que recomiendo porque, entre otras ventajas, permite construir el objeto de forma manual y usarlo en los tests.

Vamos a comprobar la inyección con 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 String 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());
                return "hello";
	}

}

No obstante, 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)

CDI requiere la existencia del constructor sin argumentos para poder crear un proxy para la clase. 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 aplicar 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 descartado por el contenedor cuando su ciclo de vida llegue a su fin.

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

Factorías

Si bien con @PostConstruct podemos configurar los objetos a medida que van siendo creados por el contenedor, también es posible escribir el código que creará los objetos. Esto permite, por ejemplo,

  • Construir objetos 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\o que no podemos modificar o extender, por ejemplo, para añadir un método @PostConstruct.

Esta creación se define en una especie de método “factoría” anotado con @Produces. Opcionalmente, indicaremos el ámbito (si no se aplicará el valor predeterminado @Dependent) del objeto creado. También podemos añadir un calificador (@Named o uno personalizado) cuando sea necesario.

A modo de ejemplo, creemos 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");
	}

}

Lo inyectamos en nuestro servicio REST siguiendo la praxis habitual.

@Inject
private List<String> stringList;

Si en un método “productor” necesitamos recibir algunos objetos de CDI, podemos inyectarlos añadiéndolos como parámetros. Si fuera necesario, se pueden anotar con calificadores.

 @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. Este método debe recibir como primer parámetro el objeto, anotado con @Disposes, y a continuación y de forma opcional las dependencias que necesitemos. 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 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 es un método anotado con @AroundInvoke. 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
	private 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. A la hora de devolver la respuesta, tenemos dos opciones.

  • Invocaremos a context.proceed() y devolvemos su resultado para continuar con la llamada al método original.
  • Devolvemos null si queremos abortar la ejecución del método interceptado. Esto permite escribir interceptores que decidan si un método debe ejecutarse.

El interceptor se aplica a cualquier CDI bean usando 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 en el que los pongamos.

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

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. Salir /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

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