Ciclo de vida del Bean de Spring

Hoy vamos a analizar el Ciclo de Vida de los Beans de Spring. Los Beans de Spring son la parte más importante de cualquier aplicación de Spring. El ApplicationContext de Spring es responsable de inicializar los Beans de Spring definidos en el archivo de configuración de beans de Spring.

Ciclo de Vida de los Beans de Spring

El Contexto de Spring también es responsable de inyectar dependencias en el bean, ya sea a través de métodos setter o constructor o mediante la autoconexión de Spring. A veces queremos inicializar recursos en las clases de bean, por ejemplo, crear conexiones de base de datos o validar servicios de terceros en el momento de la inicialización antes de cualquier solicitud del cliente. El framework de Spring proporciona diferentes formas a través de las cuales podemos proporcionar métodos de post-inicialización y pre-destrucción en el ciclo de vida de un bean de Spring.

  1. Al implementar las interfaces InitializingBean y DisposableBean – Ambas interfaces declaran un único método donde podemos inicializar/cerrar recursos en el bean. Para la post-inicialización, podemos implementar la interfaz InitializingBean y proporcionar la implementación del método afterPropertiesSet(). Para la pre-destrucción, podemos implementar la interfaz DisposableBean y proporcionar la implementación del método destroy(). Estos métodos son métodos de devolución de llamada y son similares a las implementaciones de los listeners de servlet. Este enfoque es simple de usar, pero no se recomienda porque creará un acoplamiento fuerte con el framework Spring en nuestras implementaciones de bean.
  2. Proporcionar los valores de atributo init-method y destroy-method para el bean en el archivo de configuración de bean de Spring. Este es el enfoque recomendado debido a la falta de dependencia directa con el framework Spring y podemos crear nuestros propios métodos.

Tenga en cuenta que tanto los métodos post-init como pre-destroy no deben tener argumentos, pero pueden lanzar excepciones. También necesitaríamos obtener la instancia del bean del contexto de la aplicación de Spring para la invocación de estos métodos.

Ciclo de Vida del Bean de Spring – @PostConstruct, @PreDestroy Anotaciones

El framework Spring también admite las anotaciones @PostConstruct y @PreDestroy para definir métodos de post-inicialización y pre-destrucción. Estas anotaciones forman parte del paquete javax.annotation. Sin embargo, para que estas anotaciones funcionen, necesitamos configurar nuestra aplicación Spring para que busque las anotaciones. Podemos hacer esto ya sea definiendo un bean del tipo org.springframework.context.annotation.CommonAnnotationBeanPostProcessor o mediante el elemento context:annotation-config en el archivo de configuración de beans de Spring. Escribamos una aplicación Spring simple para mostrar el uso de las configuraciones anteriores para el ciclo de vida de los beans de Spring. Cree un proyecto Maven de Spring en Spring Tool Suite, el proyecto final se verá como la imagen a continuación.

Ciclo de Vida de los Beans de Spring – Dependencias de Maven

No necesitamos incluir dependencias adicionales para configurar los métodos del ciclo de vida del bean de Spring, nuestro archivo pom.xml es como cualquier otro proyecto Maven de Spring estándar.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples</groupId>
  <artifactId>SpringBeanLifeCycle</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>

		<!-- Generic properties -->
		<java.version>1.7</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

		<!-- Spring -->
		<spring-framework.version>4.0.2.RELEASE</spring-framework.version>

		<!-- Logging -->
		<logback.version>1.0.13</logback.version>
		<slf4j.version>1.7.5</slf4j.version>

	</properties>
	
	<dependencies>
		<!-- Spring and Transactions -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>

		<!-- Logging with SLF4J & LogBack -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
			<scope>runtime</scope>
		</dependency>

	</dependencies>	
</project>

Ciclo de Vida de los Beans de Spring – Clase de Modelo

Vamos a crear una clase de Java Bean simple que se utilizará en las clases de servicio.

package com.journaldev.spring.bean;

public class Employee {

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
}

Ciclo de vida del Bean de Spring – InitializingBean, DisposableBean

Creemos una clase de servicio donde implementaremos ambas interfaces para los métodos post-init y pre-destroy.

package com.journaldev.spring.service;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import com.journaldev.spring.bean.Employee;

public class EmployeeService implements InitializingBean, DisposableBean{

	private Employee employee;

	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}
	
	public EmployeeService(){
		System.out.println("EmployeeService no-args constructor called");
	}

	@Override
	public void destroy() throws Exception {
		System.out.println("EmployeeService Closing resources");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("EmployeeService initializing to dummy value");
		if(employee.getName() == null){
			employee.setName("Pankaj");
		}
	}
}

Ciclo de vida del Bean de Spring – Post-inicialización personalizada, pre-destrucción

Dado que no queremos que nuestros servicios tengan dependencia directa del marco de trabajo de Spring, creemos otra forma de clase de servicio de empleado donde tendremos métodos del ciclo de vida de Spring post-init y pre-destroy, y los configuraremos en el archivo de configuración del Bean de Spring.

package com.journaldev.spring.service;

import com.journaldev.spring.bean.Employee;

public class MyEmployeeService{

	private Employee employee;

	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}
	
	public MyEmployeeService(){
		System.out.println("MyEmployeeService no-args constructor called");
	}

	//método pre-destroy
	public void destroy() throws Exception {
		System.out.println("MyEmployeeService Closing resources");
	}

	//método post-init
	public void init() throws Exception {
		System.out.println("MyEmployeeService initializing to dummy value");
		if(employee.getName() == null){
			employee.setName("Pankaj");
		}
	}
}

Investigaremos el archivo de configuración del Bean de Spring en un momento. Antes de eso, creemos otra clase de servicio que utilizará las anotaciones @PostConstruct y @PreDestroy.

Ciclo de vida del bean de Spring – @PostConstruct, @PreDestroy

A continuación se muestra una clase simple que se configurará como bean de Spring y para los métodos de post-inicialización y pre-destrucción, estamos utilizando las anotaciones @PostConstruct y @PreDestroy.

package com.journaldev.spring.service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class MyService {

	@PostConstruct
	public void init(){
		System.out.println("MyService init method called");
	}
	
	public MyService(){
		System.out.println("MyService no-args constructor called");
	}
	
	@PreDestroy
	public void destory(){
		System.out.println("MyService destroy method called");
	}
}

Ciclo de vida del bean de Spring – Archivo de configuración

Vamos a ver cómo configuramos nuestros beans en el archivo de contexto de Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- Not initializing employee name variable-->
<bean name="employee" class="com.journaldev.spring.bean.Employee" />

<bean name="employeeService" class="com.journaldev.spring.service.EmployeeService">
	<property name="employee" ref="employee"></property>
</bean>

<bean name="myEmployeeService" class="com.journaldev.spring.service.MyEmployeeService"
		init-method="init" destroy-method="destroy">
	<property name="employee" ref="employee"></property>
</bean>

<!-- initializing CommonAnnotationBeanPostProcessor is same as context:annotation-config -->
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
<bean name="myService" class="com.journaldev.spring.service.MyService" />
</beans>

Observe que no estoy inicializando el nombre del empleado en la definición de su bean. Dado que EmployeeService está utilizando interfaces, no necesitamos ninguna configuración especial aquí. Para el bean MyEmployeeService, estamos utilizando los atributos init-method y destroy-method para informar al framework de Spring nuestros métodos personalizados para ejecutar. La configuración del bean MyService no tiene nada especial, pero como puede ver, estoy habilitando la configuración basada en anotaciones para esto. Nuestra aplicación está lista, escribamos un programa de prueba para ver cómo se ejecutan diferentes métodos.

Ciclo de Vida del Bean de Spring – Programa de Prueba

package com.journaldev.spring.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.service.EmployeeService;
import com.journaldev.spring.service.MyEmployeeService;

public class SpringMain {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");

		System.out.println("Spring Context initialized");
		
		//MyEmployeeService service = ctx.getBean("myEmployeeService", MyEmployeeService.class);
		EmployeeService service = ctx.getBean("employeeService", EmployeeService.class);

		System.out.println("Bean retrieved from Spring Context");
		
		System.out.println("Employee Name="+service.getEmployee().getName());
		
		ctx.close();
		System.out.println("Spring Context Closed");
	}

}

Cuando ejecutamos el programa de prueba anterior, obtenemos la siguiente salida.

Apr 01, 2014 10:50:50 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@c1b9b03: startup date [Tue Apr 01 22:50:50 PDT 2014]; root of context hierarchy
Apr 01, 2014 10:50:50 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
EmployeeService no-args constructor called
EmployeeService initializing to dummy value
MyEmployeeService no-args constructor called
MyEmployeeService initializing to dummy value
MyService no-args constructor called
MyService init method called
Spring Context initialized
Bean retrieved from Spring Context
Employee Name=Pankaj
Apr 01, 2014 10:50:50 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@c1b9b03: startup date [Tue Apr 01 22:50:50 PDT 2014]; root of context hierarchy
MyService destroy method called
MyEmployeeService Closing resources
EmployeeService Closing resources
Spring Context Closed

Puntos Importantes del Ciclo de Vida del Bean de Spring:

  • A partir de la salida en la consola, queda claro que el Contexto de Spring primero utiliza un constructor sin argumentos para inicializar el objeto del bean y luego llama al método post-inicialización.
  • El orden de inicialización del bean es el mismo que se define en el archivo de configuración del bean de Spring.
  • El contexto se devuelve solo cuando todos los beans de Spring se inicializan correctamente con las ejecuciones del método post-inicialización.
  • El nombre del empleado se imprime como “Pankaj” porque se inicializó en el método post-inicialización.
  • Cuando se cierra el contexto, los beans se destruyen en el orden inverso en el que se inicializaron, es decir, en orden LIFO (último en entrar, primero en salir).

Puede descomentar el código para obtener el bean del tipo MyEmployeeService y confirmar que la salida será similar y seguirá todos los puntos mencionados anteriormente.

Interfaces Conscientes de Spring

A veces necesitamos objetos del Framework Spring en nuestros beans para realizar algunas operaciones, por ejemplo, leer parámetros ServletConfig y ServletContext o conocer las definiciones de beans cargadas por el ApplicationContext. Es por eso que el framework Spring proporciona un conjunto de interfaces *Aware que podemos implementar en nuestras clases de bean. org.springframework.beans.factory.Aware es la interfaz de marcador raíz para todas estas interfaces Aware. Todas las interfaces *Aware son subinterfaces de Aware y declaran un único método setter que debe ser implementado por el bean. Luego, el contexto de Spring utiliza la inyección de dependencias basada en setter para inyectar los objetos correspondientes en el bean y ponerlos a disposición de nuestro uso. Las interfaces Aware de Spring son similares a los escuchadores de servlets con métodos de devolución de llamada e implementan el patrón de diseño observador. Algunas de las interfaces Aware importantes son:

  • ApplicationContextAware – para inyectar el objeto ApplicationContext, un ejemplo de uso es obtener el array de nombres de definiciones de beans.
  • BeanFactoryAware – para inyectar el objeto BeanFactory, un ejemplo de uso es verificar el ámbito de un bean.
  • BeanNameAware – para conocer el nombre del bean definido en el archivo de configuración.
  • ResourceLoaderAware – para inyectar el objeto ResourceLoader, el ejemplo de uso es obtener el flujo de entrada para un archivo en el classpath.
  • ServletContextAware – para inyectar el objeto ServletContext en una aplicación MVC, el ejemplo de uso es leer parámetros y atributos del contexto.
  • ServletConfigAware – para inyectar el objeto ServletConfig en una aplicación MVC, el ejemplo de uso es obtener parámetros de configuración del servlet.

Vamos a ver el uso de estas interfaces Aware en acción implementando algunas de ellas en una clase que configuraremos como bean de Spring.

package com.journaldev.spring.service;

import java.util.Arrays;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;

public class MyAwareService implements ApplicationContextAware,
		ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
		BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware {

	@Override
	public void setApplicationContext(ApplicationContext ctx)
			throws BeansException {
		System.out.println("setApplicationContext called");
		System.out.println("setApplicationContext:: Bean Definition Names="
				+ Arrays.toString(ctx.getBeanDefinitionNames()));
	}

	@Override
	public void setBeanName(String beanName) {
		System.out.println("setBeanName called");
		System.out.println("setBeanName:: Bean Name defined in context="
				+ beanName);
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		System.out.println("setBeanClassLoader called");
		System.out.println("setBeanClassLoader:: ClassLoader Name="
				+ classLoader.getClass().getName());
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		System.out.println("setResourceLoader called");
		Resource resource = resourceLoader.getResource("classpath:spring.xml");
		System.out.println("setResourceLoader:: Resource File Name="
				+ resource.getFilename());
	}

	@Override
	public void setImportMetadata(AnnotationMetadata annotationMetadata) {
		System.out.println("setImportMetadata called");
	}

	@Override
	public void setEnvironment(Environment env) {
		System.out.println("setEnvironment called");
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		System.out.println("setBeanFactory called");
		System.out.println("setBeanFactory:: employee bean singleton="
				+ beanFactory.isSingleton("employee"));
	}

	@Override
	public void setApplicationEventPublisher(
			ApplicationEventPublisher applicationEventPublisher) {
		System.out.println("setApplicationEventPublisher called");
	}

}

Archivo de configuración de ejemplo de Spring *Aware

Archivo de configuración de bean de Spring muy simple.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean name="employee" class="com.journaldev.spring.bean.Employee" />

<bean name="myAwareService" class="com.journaldev.spring.service.MyAwareService" />

</beans>

Programa de prueba de Spring *Aware

package com.journaldev.spring.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.service.MyAwareService;

public class SpringAwareMain {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-aware.xml");

		ctx.getBean("myAwareService", MyAwareService.class);
		
		ctx.close();
	}

}

Ahora, cuando ejecutamos la clase anterior, obtenemos la siguiente salida.

Apr 01, 2014 11:27:05 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@60a2f435: startup date [Tue Apr 01 23:27:05 PDT 2014]; root of context hierarchy
Apr 01, 2014 11:27:05 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-aware.xml]
setBeanName called
setBeanName:: Bean Name defined in context=myAwareService
setBeanClassLoader called
setBeanClassLoader:: ClassLoader Name=sun.misc.Launcher$AppClassLoader
setBeanFactory called
setBeanFactory:: employee bean singleton=true
setEnvironment called
setResourceLoader called
setResourceLoader:: Resource File Name=spring.xml
setApplicationEventPublisher called
setApplicationContext called
setApplicationContext:: Bean Definition Names=[employee, myAwareService]
Apr 01, 2014 11:27:05 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@60a2f435: startup date [Tue Apr 01 23:27:05 PDT 2014]; root of context hierarchy

La salida en la consola del programa de prueba es fácil de entender, no profundizaré mucho en eso. Eso es todo sobre los métodos del ciclo de vida del bean de Spring y la inyección de objetos específicos del framework en los beans de Spring. Por favor, descargue el proyecto de ejemplo desde el siguiente enlace y analícelo para aprender más sobre ellos.

Descargar el Proyecto del Ciclo de Vida de Spring Bean

Source:
https://www.digitalocean.com/community/tutorials/spring-bean-life-cycle