
La primera especificación de Jakarta EE\ Java EE que vamos a estudiar en profundidad es una de las más importantes aunque suele pasar desapercibida en comparación con Jakarta Persistence o Jakarta REST. Pero Jakarta CDI, abreviatura de «Contexts and dependency injections» (inyección de contextos y dependencias), es el pilar sobre el que construiremos nuestras aplicaciones.
Inyección de dependencias
Antes de nada, tenemos que comprender en qué consiste la inyección de dependencias, uno de los patrones de diseño basado en el principio «Inversión de control» (IoC) y el corazón de la especificación CDI. Mucha atención, pues es un concepto extraño la primera vez que nos acercamos a él.
Empecemos con un ejemplo. Supongamos que existe una clase, llamada A, que tiene que realizar ciertas operaciones con los datos que le proporciona un objeto de otra clase, por ejemplo, B. Siguiendo la estrategia «tradicional», A tendría que crear una instancia de B llamando a su constructor o bien empleando un patrón de diseño creacional, por ejemplo, algún tipo de factoría.

Con inyección de dependencias, la clase A no tiene que instanciar un objeto de la clase B, que a partir de ahora vamos a considerar como una dependencia de A. Lo recibe en tiempo de ejecución mediante, por ejemplo, su constructor o un método setter. Dicho de otro modo, una instancia de B es «inyectada» en A mágicamente. El control se invierte en lo que respecta a la creación de objetos de forma un tanto desconcertante: en lugar de tenerlo la clase en la que necesitamos la dependencia, lo asume un actor externo (una clase, marco de trabajo, etc.) representado en la siguiente ilustración por la caja verde.

La clase A utiliza los métodos de B, pero no sabe cómo construir u obtener un objeto de B; eso ha dejado de ser una responsabilidad suya. Y construir B puede ser una tarea complicada si requiere de varias dependencias que a su vez precisan otras, y así sucesivamente.
La abstracción anterior puede llevarse un paso más allá si las funcionalidades que ofrece B se modelan con una interfaz, llamémosla iB. Ahora, en A no pediremos la inyección de un objeto de B, sino de uno que implemente iB. Al trabajar con la abstracción iB se elimina el acoplamiento entre clases, ya que no habrá ninguna relación entre A y B. De hecho, A desconoce por completo la existencia de cualquier implementación de iB.

El principal beneficio que obtenemos de esta práctica es que nos ayuda a construir los programas con módulos independientes e intercambiables. Y esto resulta formidable porque facilita la comprensión, la reutilización y, sobre todo, la sostenibilidad del código a lo largo del tiempo. También es más sencillo realizar pruebas de integración porque las dependencias pueden tener versiones específicas para los tests: seremos capaces de inyectar dobles de tests (mocks y similares) cuando sea necesario y sin modificar el código de la aplicación.
La técnica anterior («depender de abstracciones») se incluye en el celéberrimo —e imprescindible— libro «Código limpio» de Robert Martin, y se explica con detalle en la obra «Agile Software Development: Principles, Patterns, and Practices» del mismo autor. Forma parte de los principios SOLID tan en boga hoy en día. En concreto, es la D del acrónimo «Dependency Inversion». Combinando este principio con la inyección de dependencias tenemos una apuesta ganadora.
La especificación Jakarta CDI
Todo lo anterior está muy bien, pero ¿cómo programamos la «caja verde» de las figuras?
En nuestro caso, no hará falta. Será una implementación compatible con la especificación Jakarta CDI la responsable de crear, inyectar y destruir los objetos de las clases que van a ser utilizadas como dependencias y\o receptoras de las mismas. Estos objetos son gestionados por un componente denominado «contenedor de dependencias de CDI» y «viven» dentro de él según un ciclo de vida bien definido.
Además de la inyección, el contenedor nos ofrece funcionalidades adicionales y potentes para los objetos que gestiona, tales como la posibilidad de utilizar programación orientada a aspectos (interceptores) o un sistema de comunicación fundamentado en eventos (otro tipo de inversión de control). Pero todo a su debido tiempo.
La especificación CDI debutó en la versión 5 de JEE (2006). Al igual que otras tantas, nació como una estandarización de productos con gran adopción por parte del mercado, en este caso concreto el ya desaparecido JBoss Seam. Poco a poco, ha ido asumiendo la gestión de dependencias que realizaban las especificaciones JSF y EJB. En su versión actual puede utilizarse con facilidad en proyectos que no se ejecuten en un servidor de aplicaciones. De cara al futuro, se pretende seguir avanzando en su modularización y en la inclusión de funcionalidades de otras especificaciones, en especial EJB, que tienen más sentido que formen parte de CDI.
Tanto a nivel conceptual como técnico, la inyección de dependencias y servicios asociados (por ejemplo, eventos) que propone Jakarta CDI es muy similar a la que encontramos en Spring. Si tienes experiencia con este magnífico marco de trabajo, estos capítulos te resultarán familiares. En esencia, aprenderás a realizar tareas similares en el mundo Jakarta EE. Por eso, y sin entrar en detalles, a veces hablaré de Spring para subrayar las diferencias más significativas.
Weld es la implementación compatible con Jakarta CDI -e implementación de referencia en JEE 8- que incluye WildFly. Su desarrollo corre a cargo de Red Hat\JBoss, así que todo queda en casa. En el curso, nos vamos a ceñir al estándar y no examinaremos características exclusivas de Weld. Un producto alternativo es Apache OpenWebBeans, incluido en el servidor Apache TomEE.
CDI Beans
El primer ejemplo de inyección de dependencias con CDI ya lo vimos en el proyecto del capítulo Testing en WildFly con Arquillian. Echemos un vistazo a HelloServlet.
package com.danielme.jakartaee.arquillian;
import jakarta.inject.Inject;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(HelloServlet.URL)
public class HelloServlet extends HttpServlet {
public static final String URL = "helloServlet";
@Inject
private Message message;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter printWriter = response.getWriter();
response.setContentType("text/plain;charset=UTF-8");
printWriter.print(message.get());
}
}

La anotación @Inject (*) solicita al contenedor de CDI la recepción (inyección) en el atributo message de una instancia de Message. Se inyectará justo después de que el contenedor de servlets cree HelloServlet, de tal modo que podemos utilizarla en el método doGet para devolver la cadena que retorna Message#get sin temor a un NullPointer. Esto es posible porque la especificación Jakarta Servlet exige que los servlets en un servidor de aplicaciones admitan inyecciones CDI, tal y como recoge la documentación oficial.
(*) Esta anotación en realidad no pertenece a CDI sino a la especificación Jakarta Dependency Injection, API que tan solo define seis anotaciones.
Tenemos acceso directo al contenedor con la clase abstracta CDI. En HelloServlet podríamos recuperar Message sin necesidad de inyectarla.
Message message = CDI.current().select(Message.class).get();
Esta alternativa permite usar CDI en clases en las que no sea posible la inyección. Será raro que tengamos que recurrir a ella, pero es útil conocer esta posibilidad.
Las clases gestionadas por el contendor (e inyectables) se denominan CDI beans. ¿Cuáles son? La documentación señala que son las marcadas con alguna de las siguientes anotaciones: @Interceptor, @Decorator, @Dependent y todas aquellas de tipo @NormalScope o @Stereotype. Es el caso de Message: la anotación @ApplicationScoped es de tipo @NormalScope, e indica que Message es un CDI Bean del que solo existirá una única instancia u objeto en el contenedor. Todas estas anotaciones las iremos viendo con detalle en los próximos capítulos.
package com.danielme.jakartaee.cdi;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class Message {
private static final String HELLO_MESSAGE = "Jakarta EE rocks!!";
public String get() {
return HELLO_MESSAGE;
}
}
Este criterio de consideración de las clases como CDI Bean es el comportamiento predeterminado. Se personaliza en el fichero de configuración /src/main/webapp/WEB-INF/beans.xml con la propiedad bean-discovery-mode.
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_3_0.xsd"
version="3.0"
bean-discovery-mode="all">
</beans>
Aprovecho para recordar que los espacios de nombres de los XML definidos por Jakarta EE no son los mismos que los empleados en JEE.
Hay tres valores posibles.
- annotated (clases anotadas). La opción predeterminada que acabo de comentar. Si es lo que queremos, no es necesario crear el fichero beans.xml –salvo que lo necesitemos para hacer otras configuraciones que ya veremos–. Dejó de ser obligatorio en CDI 1.1 (JEE 7), aunque todavía algunos tutoriales indiquen lo contrario.
- none (ninguna). Desactiva CDI.
- all. Todas las clases del módulo en el que se encuentra el fichero son CDI beans.
Por lo general, se suele utilizar la tercera opción y en la mayoría de aplicaciones encontraremos un fichero beans.xml como el anterior.
Aunque usemos annotated o all, es posible excluir clases y paquetes completos.
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_3_0.xsd"
version="3.0"
bean-discovery-mode="all">
<scan>
<exclude name="com.danielme.jakartaee.cdi.Message" />
</scan>
</beans>
Si usamos este fichero en la aplicación de ejemplo, WildFly es incapaz de desplegarla porque no puede realizar la inyección de Message en HelloServlet. La excepción es la siguiente.
20:49:03,530 ERROR [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0021: Deploy of deployment "arquillian.war" was rolled back with the following failure message: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"arquillian.war\".WeldStartService" => "Failed to start service Caused by: org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type Message with qualifiers @Default at injection point [BackedAnnotatedField] @Inject private com.danielme.jakartaee.cdi.HelloServlet.message at com.danielme.jakartaee.cdi.HelloServlet.message(HelloServlet.java:0) "}}
El mensaje pone de relieve una característica de CDI: es imprescindible satisfacer todas las dependencias. En otras palabras, las dependencias no son opcionales.
Las exclusiones pueden registrarse de forma programática con la anotación @Vetoed. Se aplica a una clase tal y como cabría esperar, pero también permite ocultar todo un paquete a CDI del siguiente modo.
@Vetoed
package com.danielme.jakartaee.cdi;
import jakarta.enterprise.inject.Vetoed;
Este pequeño fragmento es el código completo del fichero package-info.java. A pesar de su extensión, los ficheros package-info no definen una clase. Se utilizan para proporcionar a nivel de paquete comentarios javadoc y anotaciones que incluyan en su «target» el tipo ElementType.PACKAGE.
Si fuera necesario utilizar un fichero beans.xml en nuestras pruebas con Arquillian, lo añadimos al artefacto del siguiente modo.
return ShrinkWrap.create(WebArchive.class, "arquillian-test.war")
.addClass(Message.class)
.addClass(HelloServlet.class)
.addAsWebInfResource(new FileAsset(new File("src/main/webapp/WEB-INF/beans.xml")), "beans.xml")
.addAsLibraries(assertjFiles);
En el ejemplo se ha usado el fichero beans.xml de la propia aplicación. Lo común será disponer de versiones específicas para usarlas en las pruebas tal y como haremos en las próximas entregas del curso.
Puntos de inyección
Una clase puede recibir sus dependencias de tres formas distintas, utilizando siempre @Inject.
- Directamente en sus atributos. Es lo más legible y habitual. No es necesario que el atributo tenga getters o setters, tampoco importa su visibilidad.
- A través de uno o varios métodos llamados «iniciadores» (podrían ser setters pero no es obligatorio) que reciban como argumentos las dependencias (todos los parámetros del método han de serlo).
@Inject
private void initDependencies(DependencyA dependencyA, DependencyB dependencyB) {
this.dependencyA = dependencyA;
this.dependencyB = dependencyB;
}
- Con un constructor que reciba todas las dependencias. Podemos tener todos los constructores que queramos, pero solo uno puede estar anotado con @Inject. Nuestro servlet de ejemplo quedaría así.
private final Message message;
@Inject
public HelloServlet(Message message) {
this.message = message;
}
Mi recomendación personal, y esto aplica también a Spring, es realizar la inyección vía constructores para disfrutar los siguientes beneficios.
- Las dependencias son inmutables y pueden declararse como tales (final) porque siempre se inician en todos los constructores de la clase. Con las otras estrategias, no. Trabajar con clases inmutables -una vez creadas, su estado no cambia- aporta grandes ventajas. Sus objetos se pueden reutilizar con seguridad, incluyendo entornos multihilos, y sabemos que siempre tienen un mismo estado que además es válido.
- Los objetos pueden crearse llamando a sus constructores. Admito que esto parece poco útil si consideramos que la clase que recibe las dependencias va a ser creada por el contenedor de CDI. Pero nos va a permitir realizar pruebas unitarias sobre esas clases: las instanciamos con los objetos que necesitemos -por ejemplo, mocks– y las probamos sin necesidad de ejecutar el contenedor para evitar el elevado consumo de tiempo que esta acción implica. Se puede conseguir lo mismo con métodos iniciadores, pero estos han de tener la visibilidad adecuada para que puedan ser llamados desde el código de pruebas y resulta más natural crear un objeto con un constructor.
- Los constructores son un buen indicador de un diseño pobre: si tienen muchos argumentos, resulta sospechoso y quizás la clase no esté bien cohesionada (*). Si vamos inyectando las dependencias como atributos, este hecho resulta menos evidente.
(*) Una clase poco cohesionada realiza muchas tareas poco relacionadas y viola el principio de responsabilidad única (Single responsability), el más importante de la orientación a objetos. Las clases cohesionadas son pequeñas y fáciles de reutilizar y mantener.
Lastimosamente, hay una limitación técnica en CDI a la hora de declarar inyecciones en constructores: las clases que se instancien como un proxy o representante (veremos esto en el próximo capítulo) requieren un constructor no privado sin argumentos para que el contenedor pueda crear ese proxy. Si este mandato no se respeta, Weld mostrará un error como el siguiente.
WELD-001435: Normal scoped bean class com.danielme.jakartaee.cdi.scope.ConversationScopedDependency is not proxyable because it has no no-args constructor
Este requisito lo impone la especificación para facilitar el desarrollo de las implementaciones y, por tanto, Weld no tiene más remedio que cumplirlo. No obstante, en la práctica no necesita el constructor vacío y la restricción se puede desactivar dando a la propiedad org.jboss.weld.construction.relaxed el valor true en el fichero /src/main/resources/weld.properties. Si queremos respetar el estándar y desarrollar aplicaciones portables entre servidores, debemos descartar esta configuración, aunque lo cierto es que la mayoría de servidores de aplicaciones optan por Weld como proveedor de CDI.
Jakarta CDI en IntelliJ
IntelliJ Ultimate incluye una pequeña herramienta que nos ayuda a navegar por los CDI Beans declarados en nuestras aplicaciones. A través del menú View->Tool Windows->CDI se activa esta vista.

Código de ejemplo
El código de ejemplo para este capítulo se encuentra en GitHub (todos los proyectos están en un único repositorio). Para más información sobre cómo utilizar GitHub, consultar este artículo.