Servicios y componentes complejos para pruebas unitarias con Spring

Una parte muy tediosa del desarrollo en J2EE es cuando tenemos que crear un componente que utiliza servicios del contenedor y que necesitamos probar de manera unitaria. Una manera de hacer esto siempre es subir el componente al contenedor y ahi poner alguna interfaz para invocar todos sus métodos (ya sea que lo hagamos nosotros solos o con ayuda de un framework de pruebas unitarias como JUnit). Pero normalmente esto puede quitarnos mucho tiempo, ya que hay que tener un contenedor corriendo, que consume muchos recursos, y subir el componente ahi, para que tenga todo el contexto de los servicios que va a necesitar, tales como un DataSource, correo, EJB's, etc.

En un artículo anterior expliqué cómo se pueden conectar varios componentes con Spring. Esto nos puede ahorrar todo el código para conectarlos, lo cual es muy útil cuando la conexión va más allá de una simple invocación de un método setter, como por ejemplo cuando hay que utilizar un ServiceLocator para obtener la referencia a un EJB publicado en JNDI que puede estar de manera remota, lo cual puede ser algunas lineas de código y cuando un componente usa varios otros componeontes, se vuelve algo tedioso y por lo mismo podemos introducir varios defectos en esa parte de nuestro código.

Pues bien, Spring permite definir beans que realmente son objetos publicados en JNDI, usando un proxy. Simplemente nuestro código ahora sí puede tener un método setter que recibe como parámetro la interfaz implementada por el EJB o servicio remoto, y por medio de Spring haremos la conexión. Esto nos ahorra todo el código del ServiceLocator (incluso su implementación; dicho patrón de diseño lo estaremos implementando por medio de Spring con un par de líneas de XML en vez de hacer una clase completa para ello).

Voy a ilustrar esto con un par de ejemplos. El primero consiste en tener un DataSource con un pool de conexiones, que es un servicio común ofrecido por cualquier contenedor J2EE pero algo difícil de implementar en un ambiente standalone.

El proyecto DBCP de Apache nos ayuda a crear un pool de conexiones a base de datos, lo cual es muy útil cuando se tiene una aplicación que interactúa mucho con base de datos y se requiere optimizar el número de conexiones que se tienen, ya que el crear una nueva conexión a la base toma algo de tiempo y consume recursos tanto en nuestra aplicación como en el RDBMS al cual nos conectamos.
Es por eso que el DBCP nos permite manejar un DataSource especial, que una vez configurado podemos usarlo como cualquier otro, es decir, simplemente le pedimos una conexión nueva y el DataSource nos devolverá una conexión lista para usarse, pero no necesariamente es una conexión nueva sino que es una conexión que se maneja dentro de un pool. El tamaño del pool es configurable junto con algunos otros parámetros, como el tiempo para invalidar una conexión, etc. De esta manera, podemos tener una aplicación donde haya por ejemplo 10 procesos que necesitan conexiones a la base de datos pero podriamos usar solamente 2 o 3 conexiones (o hasta 10 si es necesario), ya que al cerrar una conexión realmente lo único que sucede es que la estamos devolviendo al pool de conexiones disponibles, pero la conexión física a la base de datos sigue abierta. Este reciclaje de conexiones optimiza mucho el tiempo de respuesta y el uso de recursos tanto en la aplicación como en el RDBMS.
El único problema de DBCP es configurarlo, ya que puede ser un poco engorroso. A partir de la versión 1.2.2, necesitamos solamente usar la librería adicional de Commons Pool versión 1.3 y Commons Logging 1.1.
Aquí es donde una vez más Spring nos puede simplificar las cosas, ya que podemos definir el DataSource de DBCP como un bean más en el application context o bean factory de Spring y de ahí hacemos referencia al DataSource para cualquier otro objeto que lo necesite:

 

Con lo anterior estamos definiendo un DataSource que va a manejar un pool que inicialmente tendrá 2 conexiones disponibles, tendrá siempre un mínimo de 2 conexiones y un máximo de 10 conexiones. El mínimo de conexiones es para cuando hay un periodo de inactividad, las conexiones que no se están usando se pueden cerrar para ya no usar recursos innecesarios, pero se puede guardar un mínimo de conexiones disponibles para cuando se necesiten no tener que abrir una de inmediato (hasta que se necesite). El DataSource de DBCP se encargará de manejar todas las conexiones que tiene configuradas, cerrando las que ya no usa o que son inválidas porque ya no responden, creando nuevas cuando se necesitan, y sobre todo reciclando las que tiene disponibles cuando se le pide una conexión nueva.

Entonces ahora cualquier objeto que necesite un DataSource se puede definir simplemente así:

 

Y con esto tenemos una aplicación standalone (como una suite de pruebas) que usa un pool de conexiones a base de datos, similar a los que se manejan en un contenedor J2EE. Y dentro del contenedor vamos a manejar un application context de Spring un poco distinto, donde la definición del DataSource será como sigue:

 

Y con eso todos los beans que hacen referencia al DataSource, ahora tendrán un DataSource provisto por el contenedor via JNDI con el nombre java:/jdbc/blabla, en vez del DataSource de Commons DBCP que se usó para las pruebas unitarias. El código no cambia en absoluto; simplemente la configuración de los componentes en el XML.

Otro ejemplo es con el uso del servicio de JavaMail que un contenedor puede ofrecer. Para usar el servicio de JavaMail de JBoss, por ejemplo, podemos definir un bean como sigue:

 

Cualquier componente que necesita enviar correos puede usar el bean llamado   (es un componente de Spring para envio de mail, que puede usar infraestructura de JavaMail o de sendmail). En un ambiente standalone se puede definir de la siguiente manera:

 

Con eso se define un componente similar pero que en vez de usar el servicio de JavaMail provisto por el contenedor, va a definir el suyo propio, y así tenemos un componente para mail en una aplicación standalone.

Por último, un ejemplo muy importante: el administrador de transacciones. En J2EE se tiene toda la infraestructura de JTA, pero en un ambiente standalone no hay algo así. Pero al menos podemos tener un manejador de transacciones para las conexiones a base de datos. Simplemente debemos usar lo que Spring ofrece como PlatformTransactionManager, que es una abstracción del uso de transacciones y puede usar distintas infraestructuras, de manera que nuestro código ya se vuelve independiente de la implementación del manejador de transacciones que vayamos a usar y por lo tanto será más portable. Entonces, en una aplicación standalone podemos definir algo así:

 

El DataSourceTransactionManager es una implementación concreta de PlatformTransactionManager que sólo maneja transacciones en un DataSource. En este caso si nuestro dataSource es el que definimos con DBCP en ambiente standalone, podemos definir una transacción que abarque varias operaciones del JdbcTemplate y el manejador de transacciones se encargará de que siempre se use la misma conexión y que no se pierda. Pero en un ambiente J2EE, si queremos usar JTA, simplemente cambiamos la definición a lo que sigue:

 

Si usamos el transaction manager solamente para manejar transacciones a base de datos, nuestro código no va a cambiar. Hay otras maneras más eficientes y mejores de usar los TransactionManagers de Spring pero ese será tema de otro artículo.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ferpolo

Pregunta sobre las sessiones a la DB

Una pregunta estas sessiones si se utiliza la clase org.springframework.jndi.JndiObjectFactoryBean, se quedan abiertas en la base de datos, ya que en unas pruebas que se realizaron se ven que estas se quedan abiertas, es parte del comportamiento comun de esta forma de conexion.

Imagen de ezamudio

sesiones?

Sesiones con JndiObjectFactoryBean? Qué tipo de sesiones? Si te refieres a sesiones de Hibernate por ejemplo, pues a menos que tú las mandes a cerrar, o el framework que usas las cierre por ti, sí, se van a quedar abiertas.

Si te refieres a conexiones a base de datos, pues si lo que estás accediendo por JNDI es un pool de conexiones a base de datos que tiene tu contenedor, entonces sí, es normal que se queden abiertas (esa es la idea del pool de conexiones, tener conexiones ya abiertas disponibles para usarlas cuando se necesiten, en vez de tener que abrirlas al momento).