6 de febrero de 2011

Spring 3 - Parte 4: Ciclo de Vida de los Beans

En los tutoriales anteriores hemos visto cómo trabajar configurar beans, tanto con anotaciones como con archivo de configuración en XML, y cómo cablearlos a las propiedades de otros beans.

Sin embargo Spring nos proporciona mecanismos para controlar la creación de beans y ejecutar ciertas acciones en las distintas etapas del ciclo de vida de nuestros beans.

En este tutorial aprenderemos cómo controlar cuándo deben ser creadas las instancias de los beans y a crearlos a través de un método estático de factoría, además de recibir notificaciones cuando un bean es creado y antes de que sea destruido.

Crearemos un proyecto en el que iremos probando cada uno de los conceptos que veamos. Lo primero que haremos es crear un nuevo proyecto en NetBeans. Para esto vamos al Menú "File->New Project...". En la ventana que se abre seleccionamos la categoría "Java" y en el tipo de proyecto "Java Application". Le damos una ubicación y un nombre al proyecto. Nos aseguramos que las opciones "Create Main Class" y "Set as Main Project" estén habilitadas. Presionamos el botón "Finish" y veremos aparecer en el editor nuestra clase "Main".

Agregamos la biblioteca "Spring3" que creamos en el primer tutorial de la serie. Hacemos clic derecho sobre el nodo "Libraries" del proyecto, en el menú que aparece elegimos la opción "Add Library...". En la ventana que se abre seleccionamos la biblioteca "Spring3" y presionamos el botón "Add Library". Con esto los archivos de la biblioteca deben agregarse a nuestro proyecto.

Ahora crearemos un nuevo paquete que contendrá a nuestros beans, este paquete se llamará "beans". Hacemos clic derecho en el paquete en el que se encuentra nuestra clase "Main" (que en mi caso es "ejemplos.spring.ciclovida") y en el menú que se abre seleccionamos la opción "New -> Java package..." y al nombre del paquete le agregamos "beans". Presionamos el botón "Finish" y con esto aparecerá nuestro nuevo paquete.

El proyecto debe verse así:



Crearemos, en el paquete "beans", una interface llamada "Estudiante" que será la que usaremos para realizar nuestras inyecciones de dependencias y pruebas. Esta interface solo tendrá un método llamado "presentaExamen()" que regresará un entero entre 0 y 10 que representará la calificación del Estudiante en el examen. Esta interface queda de la siguiente forma:


public interface Estudiante
{
    int presentaExamen();
}


También crearemos, en ese mismo paquete, una clase que implemente esta interface. La clase se llamará "Universitario". Su implementación del método "presentaExamen()" simplemente será regresar un número aleatorio entre 0 y 10. Esta clase queda de la siguiente forma:


public class Universitario implements Estudiante
{
    public int presentaExamen()
    {
        return (int) (Math.random() * 10.0);
    }
}


Ahora crearemos, en el paquete default (default package), el archivo de configuración de Spring, "applicationContext.xml". Para esto hacemos clic derecho sobre el nodo "Source Packages" de nuestro proyecto, y en el menú contextual que se abre seleccionamos "new -> Other...". En la ventana que se abre, seleccionamos la categoría "Other" y como tipo de archivo "Spring XML Configuration File". Le damos un nombre al archivo, en mi caso será "applicationContext". Hacemos clic en el botón "Next". En la siguiente pantalla debemos seleccionar los namespaces que queremos que tenga nuestro archivo. No seleccionaremos ninguno. Presionamos el botón "Finish" y con eso aparecerá en nuestro editor el archivo "applicationContext.xml".

Declaramos un bean llamado "estudiante" de tipo "Universitario" dentro del archivo de configuración, de la siguiente forma:


<bean id="estudiante" class="ejemplos.spring.ciclovida.beans.Universitario" />


Comencemos viendo cómo podemos controlar el momento en el que los beans se crean. A esto se le conoce como el scope del bean.


Scope de los Beans

Por default, los beans en Spring son Singletons...¿que qué es un singleton?...Singleton es un patrón de diseño con el que se asegura que solo hay una instancia de un bean en nuestra aplicación. Si, leyeron bien, una y solo una instancia del bean por aplicación (o más exactamente por ClassLoader). Esto normalmente se logra haciendo que el constructor de la clase sea privado y proporcionando un método estático de fábrica que se encarga de controlar que solo haya una instancia de la clase. No entraré más en detalles porque ese es tema de otro tutorial.

Lo importante es remarcar el hecho de que en Spring, por default, todos los beans son singletons, o sea que el contenedor crea una sola instancia de un bean, no importa cuántas veces llamemos al método "getBean" de "ApplicationContext" (siempre que llamemos al mismo bean, claro está ^_^) Spring siempre nos regresará la misma instancia.

¿Cómo podemos comprobar que esto es verdad? Muy fácilmente ^_^. En Java, cada vez que un objeto es construido, se invoca el constructor de dicho objeto, los beans son objetos; por lo tanto, cada vez que un bean es creado se invoca su constructor.

Por lo tanto, podemos poner un mensaje en el constructor para que nos muestre algo en la consola cada vez que un bean de tipo "Universitario" sea creado. Modificaremos por un momento la clase "Universitario" para agregarle un constructor, que no reciba ningún parámetro, que nos muestre un mensaje de creación del objeto. Agregaremos el siguiente constructor a la clase "Universitario":


public Universitario()
{
    System.out.println("--Construyendo un universitario--");
}
Ahora obtendremos, en nuestro método "main", un bean de este tipo haciendo uso de un "ApplicationContext" (si no recuerdan bien cómo hacer eso pueden ver cómo en el segundo tutorial de la serie):


ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");


A continuación invocaremos dos veces el método "getBean" del applicationContext para obtener una referencia al bean "estudiante", y lo mostraremos en la consola:


System.out.println("Estudiante 1: " + applicationContext.getBean("estudiante"));
System.out.println("Estudiante 2: " + applicationContext.getBean("estudiante"));


No hacemos nada especial, solo obtenemos dos veces una referencia al bean "estudiante" y lo mostramos en la consola. Nuestro método "main" queda de la siguiente forma:

public static void main(String[] args)
{
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    System.out.println("Estudiante 1: " + applicationContext.getBean("estudiante"));
    System.out.println("Estudiante 2: " + applicationContext.getBean("estudiante"));
}


Si ejecutamos nuestra aplicación obtendremos la siguiente salida en la consola:


--Construyendo un universitario--

Estudiante 1: ejemplos.spring.ciclovida.beans.Universitario@a46701
Estudiante 2: ejemplos.spring.ciclovida.beans.Universitario@a46701


Como podemos ver, solo se invocó una vez al constructor de la clase "Universitario" y, por lo tanto, solo se construyó un objeto de esta clase. Las dos veces que invocamos al método "getBean" este nos regresó las dos veces la misma instancia.

Esto suena lógico ¿no? Ya que solo declaramos una vez el bean "estudiante" solo debería crearse una vez y siempre que queramos usar este bean deberíamos obtener la misma instancia ¿cierto?

Si bien esta idea es muy correcta, algunas veces en las que necesitaremos una nueva instancia de un bean cada vez que la pidamos (que llamemos a "getBean"). Afortunadamente existe una forma de hacer esto.

Cuando declaramos un elemento "<bean>" en el archivo de configuración de Spring, tenemos la opción de declarar un scope para ese bean. Por default el valor de este elemento es "singleton". Spring proporciona 5 posibles valores para este elemento:
  • singleton – Existirá una sola instancia del bean por contenedor. Este es el scope por defáult.
  • prototype – Se creará una nueva instancia del bean por cada llamada a "getBean", o sea, cada vez que el bean vaya a ser usado.
  • request – Existirá una sola instancia del bean por cada petición HTTP; esto es, cada petición HTTP tiene su propia instancia de un bean. Este scope solo es válido cuándo se usa un contenedor de Spring con capacidades web como Spring MVC.
  • session – Existirá una instancia del bean por cada sesión HTTP. Este scope solo es válido cuando se usa un contenedor de Spring con capacidades web como Spring MVC.
  • globalSession – Existirá una instancia del bean por cada sesión global HTTP. Típicamente solo es válida cuando se trabaja con portlets. Este scope solo es válido cuando se usa un contenedor de Spring con capacidades web como Spring MVC.

En Spring 3 existe un nuevo y secreto sexto scope llamado "thread scope", pero no está registrado por default. Pueden encontrar más información de este scope en la documentación de Spring.

Veamos cada uno de estos scopes en detalle:


El scope singleton

Con este scope, como dije anteriormente, solo existe una instancia del bean por contenedor, y esta instancia es obtenida cada vez que se llama al método "getBean".

Diciéndolo de otra forma: cuándo definimos un bean, y este tiene scope definido como singleton (el default), el contenedor de IoC de Spring crea exactamente una instancia del bean definido. Esa instancia única es almacenada en un caché especial para estos beans singleton, y todas las llamadas subsecuentes para obtener ese bean, recibirán el objeto que se encuentra en el caché.


El scope prototype

Este scope es, se podría decir, el opuesto a singleton, ya que en este caso se crea una nueva instancia del bean cada vez que se solicita un bean específico, esto es, cada vez que pedimos un bean invocando al método "getBean". Como regla general, usamos el scope prototype para todos los beans con estado, y el scope singleton para todos los beans sin estado.

A diferencia de los otros scopes, Spring no administra el ciclo de vida completo de los beans prototype (esto es muy importante para lo que veremos un poco más adelante); el contenedor crea una instancia, la configura, y la ensambla, también la envía al cliente, pero después de eso Spring ya no mantiene un registro de esa instancia. Entonces, aunque los métodos de callback de inicialización del ciclo de vida son llamados en todos los objetos sin importar su scope, en el caso de los objetos prototype, los métodos de callback de destrucción NO son llamados.


Los scopes request, session, y globalSession

Los scopes request, session, y globalSession, solo están disponibles si usamos usando Spring en una aplicación web, y usamos una implementación de "ApplicationContext" que funcione en web (como "XmlWebApplicationContext"). Si intentamos usar estos scopes con alguna otra implementación (como con "ClassPathXmlApplicationContext"), obtendremos una excepción de tipo "IllegalStateException".


El scope request

Con este scope, el contenedor una nueva instancia del bean por cada petición HTTP. Cuando la petición termina de ser procesada, el bean es descartado.


El scope session

Los beans con scope "session" son creados cada vez que se crea una sesión HTTP, y vive mientras la sesión permanezca viva, o sea que cuándo la sesión se descarta, el bean almacenado en esa sesión también es descartado.


El scope globalSession

El scope "globalSession" es similar al scope al scope "session" y solo es aplicable en el contexto de aplicaciones web basadas en portlets. La especificación de portlets define la noción de una sesión global que es compartida por todos los portlets que componen una aplicación web.

Los beans con este scope viven por el tiempo de la sesión global de los portlets.

Si estamos creando una aplicación web no-portlet (basada en servlets) y definimos algún bean con este scope, entonces se usará el scope "session" normal y no obtendremos ningún error.

Ahora que hemos visto que existen varios scopes, y las deferencias entre estos, veamos cómo arreglar el problema que teníamos anteriormente, en donde únicamente obteníamos una instancia del bean "estudiante", sin importar el número de veces que llamemos al método "getBean". En esta ocasión lo que queremos es que se cree una nueva instancia del bean cada vez que lo requiramos. Como vimos en la definición de los scopes, esto es posible lograrlo si definimos el bean con el scope "prototype", por lo que modificaremos la declaración del bean, agregándole el atributo "scope" de la siguiente forma:


<bean id="estudiante" class="ejemplos.spring.ciclovida.beans.Universitario" scope="prototype" />


Si volvemos a ejecutar nuestra aplicación, veremos la siguiente salida en la consola:


--Construyendo un universitario--
Estudiante 1: ejemplos.spring.ciclovida.beans.Universitario@1808199
--Construyendo un universitario--
Estudiante 2: ejemplos.spring.ciclovida.beans.Universitario@1bc887


Como podemos ver, en esta ocasión Spring ha construido un nuevo objeto de tipo "Universitario" por cada llamada el método "getBean".

El resto de los scopes los revisaremos cuando veamos cómo crear aplicaciones web con Spring. Ahora veamos cómo podemos crear beans usando métodos estáticos de fábrica, como se hace en el patrón de diseño factory.


Creando Beans desde Métodos Estáticos de Factory

La mayor parte del tiempo, los beans que configuramos en el contexto de aplicación de Spring son creados invocando uno de los constructores de la clase. Esto está bien si es que creamos nuestros beans siempre con constructores públicos. Pero ¿qué ocurre si por alguna razón no podemos usar un constructor público? O ¿qué pasa si usamos el API de un tercero que expone los objetos a través de un método estático de fábrica?

Afortunadamente Spring nos permite seguir creando beans con este tipo de clases. Para ejemplificar esto veamos el siguiente bean, el cuál es una implementación del patrón de diseño singleton, del cual hablamos anteriormente.

Crearemos una nueva clase, en el paquete "beans", llamada "Sun", por Sun Microsystems ^_^ (ya saben, por el asunto de que solo hay un Sun Microsystems ñ_ñ ). La clase "Sun", como dije, será una implementación del patrón de diseño singleton. Agregaremos un método de instancia, el cual solo se encargará de regresar un cadena que podremos postrar en la consola. La clase queda de la siguiente forma:

public class Sun
{
    private static Sun instancia;

    static
    {
        instancia = new Sun();
    }

    private Sun()
    {    
    }

    public static Sun getInstancia()
    {
        return instancia;
    }

    public String getMensaje()
    {
        return "Hola a todos los desarrolladores Java";
    }
}


La clase anterior tiene un constructor privado, lo que significa que solo puede usarse desde dentro de la misma aplicación. Además tiene una instancia del mismo tipo "Sun", la cual se inicializa dentro de un método de inicialización estático, o sea que la instancia se creará en cuanto la clase se cargue en el classloader.

Como el constructor y la instancia de la clase son privadas, necesitamos una forma de obtener dicha instancia. Para eso proporcionamos un método de fábrica, el método "getInstancia", el cual nos regresará la instancia cada vez que la necesitemos.

Con las dos consideraciones anteriores, nos aseguramos de que solo existirá una instancia de la clase anterior en todo el classloader y que no se hay forma de crear otra instancia. Podemos ver que con esta medida ya no podemos crear una instancia de la clase usando el operador "new", sino que debemos usar el método "getInstancia". Entonces, ¿cómo haríamos para que Spring creara un bean de esta clase?

Para casos como este, el elemento "<bean>" proporciona un atributo llamado "factory-method". Este atributo nos permite especificar un método estático que será invocado, en vez del constructor, para crear una instancia de la clase.

Así que ahora configuraremos un bean de tipo "Sun" en el archivo de configuración, para esto agregamos un nuevo elemento "<bean>" de esta forma:


<bean id="sun" class="ejemplos.spring.ciclovida.beans.Sun" factory-method="getInstancia" />


Ahora modificaremos nuestro método "main". Seguiremos creando una instancia de "ApplicationContext" a partir del archivo "applicationContext.xml", pero ahora obtendremos una referencia al bean llamado "sun". También invocaremos a su método "getMensaje" y mostraremos la cadena que regresa en la consola, de la siguiente forma:


Sun sun = applicationContext.getBean("sun", Sun.class);
System.out.println(sun.getMensaje());


Por lo que el método "main" queda de la siguiente forma:


public static void main(String[] args)
{
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Sun sun = applicationContext.getBean("sun", Sun.class);

    System.out.println(sun.getMensaje());
}


Al ejecutar la aplicación obtendremos la siguiente salida en la consola:


Hola a todos los desarrolladores Java


Con lo cual podemos darnos cuenta de que la instancia del bean se ha creado correctamente, usando el método de fábrica, y que podemos usar sus métodos de forma normal ^_^.

Ahora veremos el ciclo de vida de un bean en Spring, y cómo recibir notificaciones durante algunos de los distintos eventos del ciclo de vida de nuestros beans.


El ciclo de vida de un bean

En una aplicación Java tradicional, el ciclo de vida de un bean es muy simple. Se usa el operador "new" para instanciar el bean, y está listo para ser usado. Una vez que dejamos de usar el bean, es elegible para ser recolectado por el garbage collector. En contraste, el ciclo de vida de un bean dentro del contenedor de Spring es un poco más elaborado. Es importante entender el ciclo de vida de un bean de Spring, ya que es posible que querramos tener ventaja de algunas de las oportunidades que Spring nos ofrece para personalizar la forma en la que un bean es creado.

La siguiente figura muestra el ciclo de vida de un bean típico que es cargado en un contenedor de tipo, y pongan atención porque esto es importante, "BeanFactory":



Después de ver esta figura, podemos darnos cuenta de que Spring realiza bastantes pasos de configuración antes de que un bean esté listo para ser usado por un, y nuevamente pongan atención, "BeanFactory". La siguiente tabla explica cada uno de estos pasos con más detalle:

PasoDescripción
1. InstanciarSpring instancia el bean
2. Llenar PropiedadesSpring inyecta las propiedades del bean
3. Establecer el nombre del beanSi el bean implementa "BeanNameAware", Spring pasa el id del bean a "setBeanName()"
4. Establecer el bean factorySi el bean implementa "BeanFactoryAware", Spring pasa el bean factory a "setBeanFactory()"
5. Post procesar (antes de la inicialización)Si hay algún "BeanPostProcessors", Spring llama a sus métodos "postProcessBeforeInitialization()"
6. Inicializar beansSi el bean implementa "InitializingBean", se llamará a su método "afterPropertiesSet()". Si el bean tiene un método "init-method" propio (que veremos en la siguiente sección), el método será llamado.
7. Post procesar (después de la inicialización)Si hay algún "BeanPostProcessors", Spring llama a sus métodos "postProcessAfterInitialization()"
8. El bean está listo para usarseEn este punto el bean está listo para ser usado por la aplicación y permanecerá en el bean factory hasta que deje de ser ocupado.
9. Destruir el beanSi el bean implementa "DisposableBean", se llama a su método "destroy()". Si el bean tiene un método "destroy-bean" propio, el método especificado será llamado.


¿Por qué recalqué el hecho de que este es el ciclo de vida de un bean en un "BeanFactory"? Pues bien, esto lo hice por el hecho de que el ciclo de vida del bean es un poco diferente dentro de un "ApplicationContext", el cual podemos ver en la siguiente imagen:



La única diferencia es que si el bean implementa la interface "ApplicationContextAware", se llama a su método "setApplicationContext".

Ahora que ya tenemos una mejor idea del ciclo de vida de los beans, veamos cómo podemos recibir notificaciones cuando nuestros beans son creados o destruidos.


Callbacks de creación y destrucción de beans

Cuando un nuevo bean es creado, puede ser necesario que debamos realizar algún proceso de inicialización para dejarlo en un estado razonable para su uso. De la misma forma, cuando ya no necesitamos un bean, y este va a ser removido del contenedor, podría ser necesario realizar algún proceso de limpieza.

Spring nos proporciona varios mecanismos para interactuar con la administración del ciclo de vida, de los beans, de nuestro contenedor; más específicamente para el momento en el que los beans son creados y en el momento en el que serán destruidos. Estos mecanismos se conocen como métodos de retrollamada, o callback en inglés.

Cada uno de estos mecanismos, como ocurre regularmente, tiene sus ventajas y sus desventajas, pero los tres son igual de fáciles de usar. Los tres mecanismos son:

  • Implementación de interfaces para recibir notificaciones.
  • Declaración de los métodos que recibirán las notificaciones, usando archivos de configuración.
  • Marcar los métodos que recibirán las notificaciones, usando anotaciones.

Implementación de Interfaces para Recibir Notificaciones

Para recibir las notificaciones de la creación o destrucción de un bean, podemos implementar las interfaces "org.springframework.beans.factory.InitializingBean" y "org.springframework.beans.factory.DisposableBean". El contenedor llama al método "afterPropertiesSet()" al momento de inicializar al vean, y a "destroy()" antes de destruir al bean, cada uno en su interface correspondiente.

Vemos un ejemplo del uso de estas interfaces. Para esto modificaremos nuestra clase "Universitario" haciendo que implemente estas dos interfaces. El método "afterPropertiesSet()" servirá para indicar cuando nuestro objeto sea creado, o sea cuando nuestro nuevo Universitario entre a la escuela ^_^. Usaremos este método para mostrar un mensaje de bienvenida en la consola; y usaremos el método "destroy()" para mostrar un mensaje de despedida, también en la consola.

La clase "Universitario" queda de la siguiente forma:


public class Universitario implements Estudiante, InitializingBean, DisposableBean
{
    public Universitario()
    {
        System.out.println("--Construyendo un universitario--");
    }

    public int presentaExamen()
    {
        return (int) (Math.random() * 10.0);
    }

    public void afterPropertiesSet() throws Exception 
    {
        System.out.println("Bienvenido universitario a tu nuevo segundo hogar.");
    }

    public void destroy() throws Exception 
    {
        System.out.println("Vuela libre con tus alas propias.");
    }    
}


Un poco dramático pero muestra la idea del ejemplo ^_^!.

Si ejecutamos nuestra aplicación, obteniendo en el método "main" nuestro bean "estudiante", veremos la siguiente salida en consola:


--Construyendo un universitario--
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@134bed0: defining beans [estudiante,sun]; root of factory hierarchy
Bienvenido universitario a tu nuevo segundo hogar.
Estudiante 1: ejemplos.spring.ciclovida.beans.Universitario@6e293a


Si observamos la salida anterior con calma, podremos observar que falta un mensaje, el mensaje correspondiente con la destrucción del bean. Esto ocurre porque Spring no sabe que la aplicación ha dejado de ejecutarse (ya que esta termina de manera súbita al terminarse el método "main") y por lo tanto no sabe cuándo llamar al método "destroy". Entonces ¿cómo hacemos para que Spring sepa cuándo es que la aplicación se termina? O dicho de otra forma ¿cómo sabe Spring cuándo debe destruir el contenedor de beans y por lo tanto llamar al método "destroy"? Afortunadamente Spring nos proporciona una manera elegante de hacer esto.

Si recuerdan estamos usando, como contenedor de beans, una clase que implementa la interface "ApplicationContext", en este caso la clase "ClassPathXmlApplicationContext". Esta clase tiene una clase base llamada "AbstractApplicationContext", la cual contiene un método "close" que nos sirve para indicarle a Spring cuándo debe cerrar el ApplicationContext, y destruir todos los beans que se encuentran en el. Modificamos nuestro método "main" para declarar nuestra variable "applicatonContext" para que haga uso de una referencia de tipo "AbstractApplicationContext", y llamamos, al final del "main", a su método "close". El método "main" queda de la siguiente forma:

AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

System.out.println("Estudiante 1: " + applicationContext.getBean("estudiante"));

applicationContext.close();


Además de este cambio debemos modificar una cosa en el archivo de configuración de Spring. En este momento el bean "estudiante" está declarado como un bean con scope "prototype", o sea que se crea uno cada vez que se llama al método "getBean". Si recuerdan en la explicación de los scopes de los beans, dijimos que los métodos de destrucción de beans no serán llamados en los beans "prototype", por lo que debemos cambiar el scope del bean para que siga siendo un singleton. Cambiamos la siguiente declaración:


<bean id="estudiante" class="ejemplos.spring.ciclovida.beans.Universitario" scope="prototype" />


Para que quede de esta forma:

<bean id="estudiante" class="ejemplos.spring.ciclovida.beans.Universitario" />


Si volvemos a ejecutar nuestra aplicación, veremos la siguiente salida en la consola:


--Construyendo un universitario--

INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1db4f6f: defining beans [estudiante,sun]; root of factory hierarchy
Bienvenido universitario a tu nuevo segundo hogar.
Estudiante 1: ejemplos.spring.ciclovida.beans.Universitario@54a328

31/01/2011 01:31:44 AM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@c1b531: startup date [Mon Jan 31 01:31:43 CST 2011]; root of context hierarchy
31/01/2011 01:31:44 AM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1db4f6f: defining beans [estudiante,sun]; root of factory hierarchy

Vuela libre con tus alas propias.


Con esto comprobamos que los métodos se han llamado en los momentos correspondientes. Sin embargo ahora nuestra aplicación ha quedado acoplada a las librerías de Spring (lo cual no queremos). Como siempre, Spring nos proporciona una forma de evitar el tener que hacer este acomplamiento, declarando en el archivo de configuración los métodos que serán usados para la inicialización y destrucción del bean.


Declaración de los métodos que recibirán las notificaciones, usando archivos de configuración

Como pasa con todo en Spring, el declarar los métodos que se usarán para la inicialización y destrucción del bean es de lo más simple. Lo único que debemos hacer es declarar, en el elemento "&lt;bean>", el atributo "init-method", para el método de inicialización, y "destroy-method", para el método de destrucción. Estos métodos pueden ser cualquiera que exista dentro de nuestra clase. Para ilustrar eso modificaremos nuestra clase "Universitario" agregando dos nuevos métodos. El primero será el método "preparaEstudiante", el cual mostrará un mensaje en la consola mostrando la inicialización del bean. El segundo método será "despideEstudiante", que también mostrará un mensaje en consola, pero indicando que el bean será destruido:


public void preparaEstudiante()
{
    System.out.println("Preparando al nuevo estudiante para entrar en la universidad.");
}
    
public void despideEstudiante()
{
    System.out.println("Finalmente podemos decir adios a este estudiante.");
}


Y declaramos, en el archivo "applicationContext.xml", que el bean "estudiante" usará estos dos métodos:


<bean id="estudiante" class="ejemplos.spring.ciclovida.beans.Universitario" init-method="preparaEstudiante" destroy-method="despideEstudiante" />


Si ejecutamos nuestra aplicación en este momento obtendremos los mensajes combinados de la implementación de las interfaces del punto anterior y de los métodos declarados en el archivo de configuración. Para evitar esto eliminaremos temporalmente la declaración de las interfaces de nuestra clase "Universitario", dejándolo de esta forma:


public class Universitario implements Estudiante
{
    public Universitario()
    {
        System.out.println("--Construyendo un universitario--");
    }

    public int presentaExamen()
    {
        return (int) (Math.random() * 10.0);
    }

    public void preparaEstudiante()
    {
        System.out.println("Preparando al nuevo estudiante para entrar en la universidad.");
    }
    
    public void despideEstudiante()
    {
        System.out.println("Finalmente podemos decir adios a este estudiante.");
    }
}


Si ejecutamos nuestra aplicación obtendremos la siguiente salida:

--Construyendo un universitario--
Preparando al nuevo estudiante para entrar en la universidad.
Estudiante 1: ejemplos.spring.ciclovida.beans.Universitario@6e293a
Finalmente podemos decir adios a este estudiante.


Como podemos ver, los métodos han sido llamados sin necesidad de implementar ninguna interface, por lo que ahora están completamente desacoplados de Spring.

Esto nos sirve bastante si tenemos una o dos clases que debemos inicializar de esta manera. Pero ¿qué ocurre si queremos realizar procesos de inicialización o destrucción en muchos beans? Si usamos alguna convención de nombres para estos métodos de inicialización y destrucción (como por ejemplo "inicializa" y "destruye") no es necesario que declaremos "init-method" y "destroy-method" en cada bean de forma individual. En vez de eso, podemos tomar ventaja de los atributos "default-init-method" y "default-destroy-method" de elemento "<beans>". De esta forma estamos configurando el contenedor de beans de Spring para que busque estos métodos de inicialización y destrucción en cada bean que declaremos en el contenedor. El contenedor de IoC de Spring llamará a estos métodos, de forma automática, cada vez que un bean sea creado o destruido. Esto también nos ayuda a usar una convención de nombres consistente para métodos callback de inicialización y destrucción ^_^.

Para ejemplificar esto, modifiquemos una vez más nuestra clase "Universitario", agregando un nuevo método de inicialización llamado "inicializa", que nuevamente mostrará un mensaje en la consola, y un método de destrucción llamado "destruye", que también mostrará un mensaje en la consola:


public void inicializa()
{
    System.out.println("Realizando inicializacion de rutina en el bean.");
}
    
public void destruye()
{
    System.out.println("Realizando destruccion de rutina en el bean.");
}


Y modificamos el archivo "applicationContext.xml" para que quede de esta forma:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       
       default-init-method="inicializa"
       default-destroy-method="destruye"
       >

      <bean id="estudiante" class="ejemplos.spring.ciclovida.beans.Universitario" />

</beans>


Si ejecutamos nuevamente nuestra aplicación obtendremos la siguiente salida en la consola:


--Construyendo un universitario--
Realizando inicializacion de rutina en el bean.
Estudiante 1: ejemplos.spring.ciclovida.beans.Universitario@6e293a
Realizando destruccion de rutina en el bean.


Que como podemos ver, ha ejecutado los métodos de inicialización y destrucción por default de manera correcta.

¿Qué pasa si indicamos dos métodos, uno con "init-method" y otro con "default-init-method"? En ese caso, el método más específico ("init-method" que se indica a nivel de bean) toma precedencia sobre el método más general.

Estas dos formas de configurar los métodos de inicialización y destrucción de beans es muy buena, pero ¿qué ocurre si estamos trabajando con anotaciones? Afortunadamente también tenemos una forma de indicar estos métodos callback en caso de que estemos usando anotaciones.


Marcar los métodos que recibirán las notificaciones, usando anotaciones.

Spring (bueno, en realidad la especificación JEE) proporciona un par de anotaciones para el ciclo de vida. Para que estas anotaciones tengan efecto, debemos configurar nuestra aplicación para el uso de anotaciones. Si no recuerdan cómo hacer eso, pueden revisar el tutorial de la configuración del contenedor de IoC de Spring.

Una vez que tenemos nuestro bean, lo único que debemos hacer es marcar el método de inicialización usando la anotación "@PostConstruct", y el método de destrucción usando la anotación "@PreDestroy", de la siguiente forma:


@PostConstruct
public void inicializa()
{
    System.out.println("Realizando inicializacion de rutina en el bean.");
}
    
@PreDestroy
public void destruye()
{
    System.out.println("Realizando destruccion de rutina en el bean.");
}


Cuando ejecutemos esta aplicación veremos aparecer la siguiente salida en la consola:


--Construyendo un universitario--
Realizando inicializacion de rutina en el bean.
Estudiante 1: ejemplos.spring.ciclovida.beans.Universitario@187814
Realizando destruccion de rutina en el bean.


Que como podemos ver nos indica que esta configuración también ha funcionado adecuadamente ^_^.


Combinando Mecanismos de Callback del Ciclo de Vida

Como vimos, tenemos 3 opciones para recibir notificaciones sobre la creación y destrucción del bean. Si es necesario, tenemos la posibilidad de combinar estos tres mecanismos en el mismo bean.

En el caso de la construcción del bean, las llamadas a los distintos métodos se realizan en el siguiente orden:

  • Métodos marcados con la anotación "@PostConstruct".
  • El método "afterPropertiesSet", de la interface "InitializingBean".
  • El método que indiquemos en el archivo de configuración (recordemos que si indicamos un valor para "init-method", este toma precedencia sobre "default-init-method").

En la destrucción del bean, los métodos se ejecutan en el mismo orden:

  • Métodos marcados con la anotación "@PreDestroy".
  • El método "destroy" de la interface "DisposableBean".
  • El método que indiquemos en el archivo de configuración ("destroy-method" o "default-destroy-method").

Si por alguna razón marcáramos un método para usar más de un mecanismo (digamos que marcáramos el método "afterPropertiesSet", de la interface "InitializingBean", con la anotación "@PostConstruct") el método ejecutará una sola vez.

Ahora que sabemos todo lo que debemos saber al respecto del ciclo de vida de los beans en Spring, podemos terminar tranquilos este tutorial ^_^.

Espero que les sea de utilidad, y como siempre no olviden dejar todas sus dudas, comentarios, y sugerencias.

Saludos.

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas: