
Tras abordar la inyección de dependencias que ofrece CDI y sus ámbitos, continuamos profundizando en el uso de esta técnica. Estudiaremos funcionalidades más avanzadas que permitirán abarcar un amplio abanico de situaciones que encontraremos en nuestro día a día desarrollando aplicaciones con Jakarta EE.
El problema con las interfaces
Uno de los grandes beneficios de la inyección de dependencias es el elevado nivel de abstracción y modularidad que obtenemos en nuestras aplicaciones al utilizar interfaces, siguiendo el principio de código limpio «depender de abstracciones, no de implementaciones». No obstante, nos hemos limitado a inyectar directamente clases para simplificar el aprendizaje inicial. En esta sección ya estamos en condiciones de dar un pasito más y realizar la inyección de interfaces, solventando la problemática que esto puede acarrear si tenemos múltiples implementaciones.
Vamos a crear una interfaz que defina un conjunto de servicios relacionados con el almacenamiento de ficheros. De momento, no necesitamos ninguna operación.
package com.danielme.jakartaee.cdi.injection.file;
public interface FileStorage {
}
Tendremos una implementación para trabajar con los archivos de forma local. Es una clase inmutable de la que solo necesitamos una instancia, así que la anotamos con @ApplicationScoped.
package com.danielme.jakartaee.cdi.injection.file;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class FileStorageLocal implements FileStorage {
}
Si inyectamos la dependencia a través de la interfaz, recibimos la instancia de FileStorageLocal.
@Inject
FileStorage fileStorage;
Pero también queremos utilizar un almacenamiento remoto.
package com.danielme.jakartaee.cdi.injection.file;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class FileStorageRemote implements FileStorage {
}
¿Cómo sabe el contenedor qué FileStorage debe inyectar? No tiene la información suficiente para decidirlo, y al desplegar la aplicación en WildFly veremos este error.
20:07:36,978 ERROR [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0021: Deploy of deployment "cdi.war" was rolled back with the following failure message: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"cdi.war\".WeldStartService" => "Failed to start service Caused by: org.jboss.weld.exceptions.DeploymentException: WELD-001409: Ambiguous dependencies for type FileStorage with qualifiers @Default at injection point [BackedAnnotatedField] @Inject com.danielme.jakartaee.cdi.file.FileService.fileStorage at com.danielme.jakartaee.cdi.file.FileService.fileStorage(FileService.java:0) Possible dependencies: - Managed Bean [class com.danielme.jakartaee.cdi.file.FileStorageRemote] with qualifiers [@Any @Default], - Managed Bean [class com.danielme.jakartaee.cdi.file.FileStorageLocal] with qualifiers [@Any @Default]
IntelliJ también nos indica esa ambigüedad.

El mensaje es bastante descriptivo e informa de las clases candidatas para resolver la inyección. Tenemos que configurar las clases y puntos de inyección para que CDI proporcione la implementación que necesitemos recurriendo al uso de calificadores (qualifiers). Se trata de anotaciones que permiten identificar las clases en el contenedor de CDI para indicar las que queremos recibir en los puntos de inyección cuando existan varias opciones. Son la solución al interrogante del siguiente diagrama

Primero veamos cómo crear calificadores a medida. Luego examinaremos algunos de los ofrecidos por la especificación.
Calificadores personalizados
Crear nuestro propio calificador es tan sencillo como definir una anotación de tipo @Qualifier. Necesitamos uno para cada FileStorage.
package com.danielme.jakartaee.cdi.injection.file;
import jakarta.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
public @interface FileStorageLocalQualifier {
}
package com.danielme.jakartaee.cdi.injection.file;
import jakarta.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
public @interface FileStorageRemoteQualifier {
}
Aunque el lector nunca haya creado una anotación, puede comprobar que es sencillo. Se realiza empleando, valga la redundancia, ciertas anotaciones. Con @Retention estamos indicando hasta qué momento son accesibles los datos: solo en el código (SOURCE), al compilar (CLASS, opción predeterminada), o siempre disponible (RUNTIME). Elegimos el último valor con el fin de que el contenedor de CDI pueda acceder a la anotación durante la ejecución de la aplicación.
Con @Target se definen los elementos anotables. Queremos permitir el uso de FileStorageRemoteQualifier en cualquier punto de inyección, así que tenemos field (atributo), method (método) y parameter (parámetro de un método).
Aplicamos estos calificadores a las clases correspondientes.
package com.danielme.jakartaee.cdi.injection.file;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@FileStorageLocalQualifier
public class FileStorageLocal implements FileStorage {
}
package com.danielme.jakartaee.cdi.injection.file;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@FileStorageRemoteQualifier
public class FileStorageRemote implements FileStorage {
}
Nuestras implementaciones ahora son identificables de forma unívoca con los calificadores que hemos creado.

Para indicar cuál de ellas queremos inyectar, volvemos a usar los calificadores.
package com.danielme.jakartaee.cdi.injection.file;
import com.danielme.jakartaee.cdi.Deployments;
import jakarta.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(ArquillianExtension.class)
class FileStorageInjectionArquillianTest {
@Inject
@FileStorageRemoteQualifier
private FileStorage fileStorageRemote;
@Inject
@FileStorageLocalQualifier
private FileStorage fileStorageLocal;
@Deployment
public static WebArchive createDeployment() {
return Deployments.services();
}
@Test
void testFileStorageIsLocal() {
assertThat(fileStorageLocal).isInstanceOf(FileStorageLocal.class);
}
@Test
void testFileStorageIsRemote() {
assertThat(fileStorageRemote).isInstanceOf(FileStorageRemote.class);
}
}
Nota. La creación de los artefactos a desplegar por Arquillian se ha encapsulado en la clase Deployments.
A continuación se muestra el uso del calificador en los otros puntos de inyección posibles, acompañando siempre a @Inject.
- En un constructor.
private FileStorage fileStorageLocal;
@Inject
public FileService(@FileStorageLocalQualifier FileStorage fileStorageLocal) {
this.fileStorageLocal = fileStorageLocal;
}
- En un método iniciador o un setter.
FileStorage fileStorageLocal;
@Inject
public void setFileStorage(@FileStorageLocalQualifier FileStorage fileStorageLocal) {
this.fileStorageLocal = fileStorageLocal;
}
@Named
La forma más cómoda de identificar una clase consiste en asignarle un nombre único con la anotación @Named de la API Jakarta Dependency Injection. Si no se indica este nombre, se usará el de la clase.
Vamos a comprobarlo con una nueva interfaz que ofrece, por ejemplo, acceso a servicios de consulta de información sobre películas.
package com.danielme.jakartaee.cdi.movie;
public interface MovieProvider {
}
Creamos dos implementaciones con el calificador @Named.
package com.danielme.jakartaee.cdi.injection.movie;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
@ApplicationScoped
@Named
public class MovieImdbProvider implements MovieProvider {
}
package com.danielme.jakartaee.cdi.injection.movie;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
@ApplicationScoped
@Named
public class MovieTMDbProvider implements MovieProvider {
}
Repetimos la praxis de la sección anterior y usamos el calificador en el punto de inyección. Si se trata de un atributo y no indicamos el nombre de la implementación, se usará el del propio atributo. Comprobémoslo con una prueba.
package com.danielme.jakartaee.cdi.injection.movie;
import com.danielme.jakartaee.cdi.Deployments;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(ArquillianExtension.class)
class MovieInjectionArquillianTest {
@Inject
@Named
private MovieProvider movieImdbProvider;
@Inject
@Named
private MovieProvider movieTMDbProvider;
@Deployment
public static WebArchive createDeployment() {
return Deployments.movies();
}
@Test
void testProviderIsImdb() {
assertThat(movieImdbProvider).isInstanceOf(MovieImdbProvider.class);
}
@Test
void testProviderIsTMDb() {
assertThat(movieTMDbProvider).isInstanceOf(MovieTMDbProvider.class);
}
}
Esta convención solo funciona si hacemos la inyección en un atributo.
WELD-001427: Only field injection points can use the @Named qualifier with no value
En un método, es obligatorio proveer el nombre del siguiente modo.
private MovieProvider movieImdbProvider;
@Inject
public MovieService(@Named("movieImdbProvider") MovieProvider movieImdbProvider) {
this.movieImdbProvider= movieImdbProvider;
}
En cualquier caso, lo más fiable es establecer siempre un nombre tanto al definir la clase como la inyección. De este modo, no encontraremos problemas si se modifica el nombre de la clase o el atributo, y/o la persona que lee nuestro código no es consciente de esta convención. Con este cambio, el ejemplo queda así.

@ApplicationScoped
@Named("MovieImdbProvider")
public class MovieImdbProvider implements MovieProvider {
}
@ApplicationScoped
@Named("MovieTMDbProvider")
public class MovieTMDbProvider implements MovieProvider {
}
@ExtendWith(ArquillianExtension.class)
class MovieInjectionArquillianTest {
@Inject
@Named("MovieImdbProvider")
private MovieProvider movieImdbProvider;
@Inject
@Named("MovieTMDbProvider")
private MovieProvider movieTMDbProvider;
Gracias a @Named nos ahorramos definir los calificadores personalizados, aunque es una tarea trivial. Pero es una solución menos elegante porque pasamos a trabajar con cadenas. Más adelante, veremos que la creación de calificadores tiene la ventaja adicional frente al uso de @Named de permitirnos configurar con sencillez una implementación predeterminada.
@Any
Todas las clases de CDI poseen este calificador, a excepción de las marcadas con @New, anotación que no veremos pues se consideró obsoleta (deprecated) en CDI 1.1 en favor de @Dependent. Lo usaremos para inyectar todas las implementaciones de una interfaz en un objeto Iterable de tipo Instance. Sirva la siguiente prueba como ejemplo.
@Any
@Inject
Instance<FileStorage> storageImplementations;
@Test
void testAnyInjectAll() {
assertThatCode(() -> storageImplementations.select(FileStorageLocal.class).get())
.doesNotThrowAnyException();
assertThatCode(() -> storageImplementations.select(FileStorageRemote.class).get())
.doesNotThrowAnyException();
}
@Default y @Alternative
@Default es un calificador asignado automáticamente a las clases que no tengan uno distinto de @Named. Debido a su nombre, cabe esperar que si hay varias implementaciones, por omisión se inyectará la anotada con @Default. Pero si no recurrimos a los calificadores personalizados e identificamos las clases con @Named, o no las identificamos en absoluto, esto no nos va a servir porque tendremos más de una implementación de tipo @Default.
Es un poco enrevesado, pero con un ejemplo queda más claro. Creemos una interfaz recurriendo al típico y socorrido ejemplo de formas geométricas.
public interface Shape {
}
public class Circle implements Shape {
}
public class Rectangle implements Shape {
}
En la siguiente clase queremos probar que Circle se inyecta por omisión, es decir, sin indicar de forma explícita un calificador.
package com.danielme.jakartaee.cdi.injection.shapes;
import com.danielme.jakartaee.cdi.Deployments;
import jakarta.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(ArquillianExtension.class)
class ShapeDefaultInjectionArquillianTest {
@Inject
private Shape shape;
@Deployment
public static WebArchive createDeployment() {
return Deployments.shapes();
}
@Test
void testShapeIsCirle() {
assertThat(shape).isInstanceOf(Circle.class);
}
}
Hemos vuelto al problema inicial, y sin ejecutar la prueba ya deberíamos prever que la inyección de Shape es ambigua porque el contenedor de CDI no sabe si usar Circle o Rectangle. Pero ahora conocemos que en realidad las clases están anotadas de esta manera.
@Default
@Any
public class Rectangle implements Shape {
}
@Default
@Any
public class Circle implements Shape {
}
Y si usamos @Named en una de ellas, tendremos esto.
@Named
@Default
@Any
public class Rectangle implements Shape {
}
@Default
@Any
public class Circle implements Shape {
}
Así pues, en ambos escenarios hay dos clases @Default. Si queremos definir una implementación predeterminada cuando tengamos más de una de tipo @Default, debemos marcar con @Alternative todas las demás. En nuestro caso, es la clase Rectangle.
@Alternative
public class Rectangle implements Shape {
}

Ahora en Shape recibimos Circle y la prueba es válida. ¿Y qué pasa con Rectangle? Para CDI, no existe y no podemos inyectarla, ni siquiera usando @Any. La única forma de utilizarla es definirla como la implementación por omisión en beans.xml en detrimento de Circle, la cual pasará a ser «invisible».
<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">
<alternatives>
<class>com.danielme.jakartaee.cdi.injection.shapes.Rectangle</class>
</alternatives>
</beans>
Con el fichero anterior (beans-alternatives.xml), incluido en el artefacto construido por el método Deployments.shapesAlternative(), esta prueba es exitosa.
@ExtendWith(ArquillianExtension.class)
class ShapeIAlternativeInjectionArquillianTest {
@Inject
private Shape shape;
@Deployment
public static WebArchive createDeployment() {
return Deployments.shapesAlternative();
}
@Test
void testShapeIsRectangle() {
assertThat(shape).isInstanceOf(Rectangle.class);
}
}
Este comportamiento de las implementaciones alternativas no parece útil a primera vista porque solo podemos utilizar una de ellas y las demás permanecen «inactivas». Sin embargo, nos permitirá crear implementaciones que usaremos en los tests de integración si necesitamos sustituir las clases «reales» de la aplicación por un objeto de tipo mock. La prueba anterior es un buen ejemplo de ello.
Otro caso en el que las alternativas resultan prácticas lo encontramos cuando las implementaciones dependen del entorno de ejecución, y en función del mismo queremos usar unas clases u otras. Por ejemplo, supongamos que nuestra aplicación tendrá una instalación propia para cada cliente. En la mayoría de ellas, el FileStorage predeterminado será el local, pero en unas pocas debe ser el remoto. Podemos personalizar la instalación con el fichero de configuración beans.xml sin tener que contemplar esta casuística en el código (*) de tal modo que cada instalación use la implementación deseada.
(*) Si esto último no supone un problema, una alternativa a esta estrategia consiste en decidir qué implementación utilizar con un método «productor», técnica que presentaré en el próximo capítulo.
@Default sí funciona de forma intuitiva cuando trabajamos con calificadores personalizados. Basta con dejar la implementación que queramos por omisión sin ningún calificador para que sea la única que recibe @Default. Veámoslo creando una nueva clase de tipo FileStorage con el calificador @Default, el cual no ponemos porque está implícito. El resto de implementaciones, recordemos, están anotadas con @FileStorageLocalQualifier y @FileStorageRemoteQualifier.
package com.danielme.jakartaee.cdi.injection.file;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class FileStorageTemp implements FileStorage {
}
Ahora tenemos tres candidatas a ser inyectadas donde solicitemos un objeto FileStorage.

En FileStorageInjectionArquillianTest comprobamos con un nuevo test que, efectivamente, FileStorageTemp se inyecta cuando no se emplea ningún calificador en el punto de inyección porque es la implementación predeterminada.
@Inject
private FileStorage fileStorageDefault;
@Test
void testFileStorageDefault() {
assertThat(fileStorageDefault).isInstanceOf(FileStorageTemp.class);
}
Hemos conseguido tener una implementación por omisión sin perder la posibilidad de inyectar cualquiera de las otras, las cuales se siguen utilizando en las demás pruebas de FileStorageInjectionArquillianTest. Las cuatro inyecciones posibles de FileStorage en esa clase quedan así.
@Inject
@FileStorageRemoteQualifier
private FileStorage fileStorageRemote;
@Inject
@FileStorageLocalQualifier
private FileStorage fileStorageLocal;
@Inject
private FileStorage fileStorageDefault;
@Any
@Inject
Instance<FileStorage> storageImplementations;
@Alternative no solo es aplicable a clases sin calificadores, sino que puede combinarse tanto con los calificadores @Named como con los personalizados. Nos permite tener varias clases con el mismo calificador personalizado o nombre dentro de CDI si todas ellas están anotadas con @Alternative excepto, claro está, la que queremos dejar como «activa» para que se inyecte.
Entre nuestras clases de ejemplo tenemos esta.
@ApplicationScoped
@Named("MovieTMDbProvider")
public class MovieTMDbProvider implements MovieProvider {
}
Hagamos otro MovieProvider, con el mismo nombre pero marcado como alternativa.
package com.danielme.jakartaee.cdi.injection.movie;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import jakarta.inject.Named;
@ApplicationScoped
@Named("MovieTMDbProvider")
@Alternative
public class MovieTMDbV2Provider implements MovieProvider {
}
El test correspondiente se sigue ejecutando con éxito a pesar de que existen dos MovieProvider con la denominación MovieTMDbProvider: Se inyecta el único que no es alternativo y que es el que espera recibir la prueba.
@Inject
@Named("MovieTMDbProvider")
private MovieProvider movieTMDbProvider;
@Test
void testProviderIsTMDb() {
assertThat(movieTMDbProvider).isInstanceOf(MovieTMDbProvider.class);
}
Este diagrama ilustra el nuevo escenario. Hay tres clases y dos opciones de inyección para MovieProvider elegibles con @Named.
Para inyectar la implementación alternativa (MovieTMDbV2Provider) ya sabemos lo que hay que hacer: definirla en el fichero beans.xml a usar en la prueba (src/test/resources/beans-movie-alternatives.xml) para que reemplace a la implementación predeterminada.
<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">
<alternatives>
<class>com.danielme.jakartaee.cdi.movie.MovieTMDbV2Provider</class>
</alternatives>
</beans>
Aplicando el fichero anterior en una nueva clase de pruebas, el siguiente test es válido.
package com.danielme.jakartaee.cdi.injection.movie;
import com.danielme.jakartaee.cdi.Deployments;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(ArquillianExtension.class)
class MovieAlternativeInjectionArquillianTest {
@Inject
@Named("MovieTMDbProvider")
private MovieProvider movieTMDbProvider;
@Deployment
public static WebArchive createDeployment() {
return Deployments.moviesAlternatives();
}
@Test
void testProviderTMDbIsAlternative() {
assertThat(movieTMDbProvider)
.isInstanceOf(MovieTMDbV2Provider.class);
}
}
Reutilizando calificadores personalizados
Escribir un calificador personalizado es bien sencillo, pero en sistemas grandes corremos el riesgo de crear una miríada de anotaciones. CDI permite usar un mismo calificador personalizado para identificar a clases distintas mediante la parametrización de la anotación. De hecho, este mecanismo es el que permite la existencia de @Named. Revisemos su declaración.
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name.
* @return the name.
*/
String value() default "";
}
No es la primera vez en el curso que creamos una anotación, pero este código presenta algo novedoso: tenemos un miembro de tipo cadena llamado value. Los campos de una anotación, de tipos primitivos o arrays, se definen como si fueran métodos -su valor se obtendrá invocando a los mismos- y darles valor es opcional si asignamos valores predeterminados -no pueden ser null- con default. El nombre value es especial: no hace falta indicarlo dentro de la anotación siempre y cuando sea el único parámetro al que le damos un valor. Por eso, las siguientes declaraciones son equivalentes.
@Named(value = "MovieImdbProvider")
@Named("MovieImdbProvider")
Las clases calificadas con @Named comparten, obviamente, el mismo calificador, pero se pueden distinguir unas de otras gracias al atributo value. La anotación es tan genérica porque value es de tipo cadena y puede ser cualquier cosa.
Probemos esta característica de CDI con un nuevo ejemplo basado en la interfaz «línea de comandos».
package com.danielme.jakartaee.cdi.injection.commandline;
public interface CommandLine {
}
En esta ocasión, contaremos con un único calificador para todas las implementaciones.
package com.danielme.jakartaee.cdi.injection.commandline;
import jakarta.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
public @interface CommandLineQualifier {
CommandLineSupported value();
}
En cierto modo, hemos escrito nuestro propio @Named, pero con una diferencia capital: cada clase se identifica por un valor del enumerado CommandLineSupported, lo que hace a nuestro código menos flexible pero más seguro. Vamos a contemplar un par de tipos de líneas de comandos.
package com.danielme.jakartaee.cdi.injection.commandline;
public enum CommandLineSupported {
POWERSHELL, BASH
}
Por tanto, y sin tener en cuenta el uso de alternativas, solo podemos identificar dos implementaciones de CommandLine con @CommandLineQualifier.
package com.danielme.jakartaee.cdi.injection.commandline;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@CommandLineQualifier(CommandLineSupported.POWERSHELL)
public class PowershellCommandLine implements CommandLine {
}
package com.danielme.jakartaee.cdi.injection.commandline;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@CommandLineQualifier(CommandLineSupported.BASH)
public class BashCommandLine implements CommandLine {
}
De forma esquemática, este diagrama muestra las posibilidades de inyección para CommandLine.

Una vez más, escribamos pruebas, equivalentes a las que vimos para FileStorage. No tienen ningún misterio; lo más relevante es la forma de inyectar CommandLine.
package com.danielme.jakartaee.cdi.injection.commandline;
import com.danielme.jakartaee.cdi.Deployments;
import jakarta.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(ArquillianExtension.class)
class CommandLineInjectionArquillianTest {
@Inject
@CommandLineQualifier(CommandLineSupported.BASH)
private CommandLine bash;
@Inject
@CommandLineQualifier(CommandLineSupported.POWERSHELL)
private CommandLine powershell;
@Deployment
public static WebArchive createDeployment() {
return Deployments.commandLine();
}
@Test
void testCommandLineIsBash() {
assertThat(bash).isInstanceOf(BashCommandLine.class);
}
@Test
void testCommandLineIsPowershell() {
assertThat(powershell).isInstanceOf(PowershellCommandLine.class);
}
}
Código de ejemplo
El código de ejemplo del 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.