CDI: Inyección de dependencias en JEE y ejecución de test de integración con el soporte de Arquilian.
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Configuración.
- 4. Soldando…
- 5. Arquilian.
- 6. Referencias.
- 7. Conclusiones.
1. Introducción
CDI o Context Dependency Injection define el mecanismo para resolver dependencias entre servicios dentro del estándar JEE, a partir de la versión 6.
CDI te da posibilidad de inyectar componentes en una aplicación de modo typesafe, incluyendo la posibilidad de seleccionar en tiempo de despliegue que implementación en particular usar.
Con el soporte de CDI podemos «declarar» cualquier bean y un punto de inyección sobre el cual se realizará la inyección del mismo cuando se requiera (luego os
cuento porque pongo declarar entrecomillado).
¿Es el IoC de Spring estandarizado?, casí… más bien es la evolución del core de Jboss Seam 2,
hecho estándar. Spring y Google Guice cumplen la especificación JSR330 (Dependency Injection) y CDI está especificado en la JSR299 (inicialmente denominada Web Beans) que incorpora a su vez la JSR330.
CDI no es más que la especificación y como con otras, véase JPA, JEE no proporciona una implementación propia, de tal modo que la implementación de referencia de
CDI es Weld, un proyecto de Red Hat que surje como la evolución del core de Jboss Seam 2.
De hecho ahora Jboss Seam 3, es JEE6 compliant puesto que su core es Weld, además de proporcionar casi todas las facilidades que Seam 2.
Así como Spring es engranaje, Weld es soldadura; y tiene cierta connotación semántica porque es cierto que con CDI la inyección de dependencias es fuertemente tipada, con lo que la relación es más fuerte.
Hay otras implementaciones de CDI, así CODI de Apache; pero en este caso más que
diversificar el futuro pasa por converger en un proyecto único que han denominado Apache DeltaSpike, la futura implementación de referencia de CDI.
Parece que no habrá más versiones de Jboss Seam y los esfuerzos irán dirigidos a desarrollar un framework común entre Apache y Red Hat.
Centrándonos en lo disponible a día de hoy, en este tutorial vamos a ver cómo configurar nuestro proyecto para que haga uso de CDI y
su implementación de referencia Weld. Así mismo, vamos a ver cómo configurar nuestro entorno de tests de integración para poder verificar
nuestros servicios en el contexto de Weld con el soporte de Arquilian.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 15′ (2.4 GHz Intel Core i7, 8GB DDR3 SDRAM).
- Sistema Operativo: Mac OS X Lion 10.7.4
- JDK 1.6.0.29
- Weld 1.1.5.Final
- Arquilian 1.0.3.Final
3. Configuración.
Como siempre, haciendo uso de maven, un primer vistazo a nuestro pom.xml:
<!-- CDI api --> <dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-6.0</artifactId> <version>1.0.0.Final</version> <type>pom</type> <scope>provided</scope> </dependency>
Sin más, la implementación la proporcionará el servidor de aplicaciones en el que despleguemos.
Lo siguiente es incluir un fichero beans.xml en el directorio WEB-INF si es una aplicación web o en el directorio META-INF en el caso de un jar; puede estar vacío (esto es herencia del seam.properties de Jboss Seam).
<beans http://java.sun.com/xml/ns/javaee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> </beans>
En el fichero beans.xml no se declaran beans, se declaran interceptores, decoradores, alternativas,… pero no los beans. Casi toda la configuración sobre
inyección de dependencias en CDI es por anotaciones.
4. Soldando…
Ya podemos comenzar a «declarar» o «publicar» beans, y lo entrecomillo porque no hay que hacer nada especial para publicar un bean en el contexto de CDI,
al igual que con Jboss Seam cualquier POJO, EJB, unidad de persistencia, recurso JNDI,… puede ser inyectado sin necesidad de declararlo como un bean.
Esto quiere decir que no necesito anotar la clase solo indicar el punto de inyección con @Inject.
public interface SecurityManager { } public class SecurityManagerImpl implements SecurityManager { } @Stateless public class SomeBean { @Inject SecurityManager securityManager; }
Estamos declarando de forma implícita el bean SecurityManagerImpl que cumple una interfaz e inyectando una referencia en un EJB de sesión sin estado; el bean inyectado tendrá por defecto el mismo ámbito que el bean en el que se inyecta.
Si queremos modificar el ámbito del bean inyectado sí podemos incluir una anotación a nivel de clase, pero no para declarar el bean en sí mismo sino su ámbito:
- @Dependent: es la anotación por defecto que indica que su ámbito depende del bean en el que se inyecte,
- @RequestScoped: el bean permanece durante la petición HTTP y es destruido al final,
- @SessionScoped: el bean es compartido por todas las peticiones HTTP que pertenecen a la misma sesión,
- @ApplicationScoped: el bean es creado en el arranque de la aplicación y destruido cuando la aplicación se detiene,
- @ConversationScoped: se puede mantener una instancia del mismo bean durante varias peticiones HTTP, demarcando el ámbito de inicio y fin de la transacción (herencia de Jboss Seam),
- @Singleton: se mantiene una instancia única del bean.
La inyección se realiza por tipo, no por nombre, de modo tal que si tenemos dos implementaciones de la misma clase y no las cualificamos se producirá una excepción en el arranque. Para evitarlo, podemos hacer uso de @Qualifier para crear nuestras propias anotaciones de cualificación de beans:
public interface SecurityManager { } @Database public class DatabaseDrivenSecurityManagerImpl implements SecurityManager { } public class SSOSecurityManagerImpl implements SecurityManager { } @Stateless public class SomeBean { @Inject @Database SecurityManager securityManager; } @Qualifier @Target({TYPE, METHOD, PARAMETER, FIELD}) @Retention(RUNTIME) public @interface Database {}
Aunque también podríamos hacer uso de la anotación @Alternative declarando el bean que prevalece en el beans.xml, de esto modo podríamos mantener una
implementación base en el entorno real y un mock declarado como alternativo en el entorno de tests.
5. Arquilian.
Una vez visto como configurar y realizar los primeros pasos para resolver dependencias, vamos a ver como configurar nuestro entorno de tests de integración.
Para ello, podemos usar un framework de test llamado Arquilian que nos permite levantar un contexto embebido de CDI.
Si habéis trabajado con Embedded JBoss para realizar tests con un contenedor de EJBs os sonarán los conceptos.
Arquilian es una plataforma de tests para la JVM que nos permite crear tests de integración y ejecutarlos en un contenedor. Sus principales características son:
- el contenedor se obtiene del classpath, no se configura,
- permite la injección de dependencias dentro de las propias clases de tests,
- se puede ejecutar desde el IDE o con el soporte de maven, y
- es el sustituto de SeamTest
Arquilian a su vez hace uso de ShrinkWrap, un framework para crear paquetes java «al vuelo» con todas las clases que estarán accesibles dentro del contexto de CDI.
Para configurar nuestro proyecto y, con el ámbito de test, no tenemos más que incluir las siguientes dependencias:
<dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-bom</artifactId> <version>1.0.3.Final</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.junit</groupId> <artifactId>arquillian-junit-container</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-weld-ee-embedded-1.1</artifactId> <version>1.0.0.CR3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.weld</groupId> <artifactId>weld-core</artifactId> <version>1.1.5.Final</version> <scope>test</scope> </dependency> </dependencies>
Con ello ya podemos declarar en nuestros test que corran bajo el soporte de Arquilian y tener disponibles los beans declarados en la empaquetación.
@RunWith(Arquillian.class) public class SecurityManagerTest { @Deployment public static JavaArchive createDeployment() { return ShrinkWrap.create(JavaArchive.class) .addClasses(SecurityManager.class, DatabaseDrivenSecurityManagerImpl.class, SSOSecurityManagerImpl.class) .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); } @Inject @Database private SecurityManager securityManager; @Test public void shouldNotReturnNull() { assertNotNull(securityManager.getCredentials()); }
Debemos declarar un método con la anotación @Deployment que devuelva un objeto de tipo JavaArchive, que podemos crear con el soporte de ShrinkWrap,
incluyendo todas las clases distponibles y, dentro del manifest, un beans.xml vacío.
6. Referencias.
- http://seamframework.org/Weld
- http://arquillian.org/
- http://myfaces.apache.org/extensions/cdi/
- http://wiki.apache.org/incubator/DeltaSpikeProposal
7. Conclusiones.
La alternativa JEE a Spring, ¿pero se puede trabajar con JEE sin Spring?, efectivamente y sí 😉
CDI también nos proporciona interceptores, decoradores y una gestión de eventos, que dejo para otros tutoriales, que vendrían
a cubrir el soporte de AOP de Spring.
Un saludo.
Jose