El framework Spring ofrece numerosos procesadores que permiten extender su funcionalidad, entre ellos: BeanPostProcessor, BeanFactoryPostProcessor, BeanValidationPostProcessor, y otros procesadores posteriores. Su mecanismo de uso es bastante similar, por lo que dominar uno de ellos facilita la comprensión de los demás.
Propósito de la interfaz BeanPostProcessor:
Si deseamos agregar lógica personalizada durante el ciclo de vida de los beans en el contenedor de Spring, específicamente después de la instanciación, configuración y antes o después de los métodos de inicialización, debemos implementar una o más clases de la interfaz BeanPostProcessor y registrarlas en el contenedor IoC de Spring.
API de BeanPostProcessor:
public interface BeanPostProcessor {
/**
* Aplica este BeanPostProcessor a la nueva instancia de bean <i>antes</i> de cualquier
* devolución de llamada de inicialización de bean (como {@code afterPropertiesSet}
* de InitializingBean o un método init personalizado). El bean ya estará poblado con valores de propiedad.
*/
// Realizar tareas de inicialización personalizadas después de la instanciación e inyección de dependencias,
// pero antes de la inicialización explícita
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
/**
* Aplica este BeanPostProcessor a la nueva instancia de bean <i>después</i> de cualquier
* devolución de llamada de inicialización de bean (como {@code afterPropertiesSet}
* de InitializingBean o un método init personalizado). El bean ya estará poblado con valores de propiedad.
*/
// Ejecutar después de completar la instanciación, inyección de dependencias e inicialización
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
La interfaz BeanPostProcessor proporciona dos métodos que los desarrolladores pueden personalizar: postProcessBeforeInitialization y postProcessAfterInitialization.
postProcessBeforeInitialization: Este método permite realizar procesamiento personalizado justo antes de que Spring invoque los métodos de inicialización del bean.
postProcessAfterInitialization: Este método permite realizar procesamiento personalizado después de que Spring ha completado la inicialización del bean.
Ejemplo de implementación:
Modelo de Mascota:
package com.ejemplo.modelo;
/**
* Bean de prueba
*/
public class Mascota {
private String nombre;
private int edad;
public void saludar() {
System.out.println("nombre:" + nombre);
System.out.println("edad:" + edad);
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
}
Configuración en el contenedor Spring:
<bean class="com.ejemplo.modelo.Mascota" id="mascota">
<property name="nombre" value="Firulais"></property>
<property name="edad" value="2"></property>
</bean>
<bean class="com.ejemplo.procesador.MascotaBeanPostProcessor" id="mascotaBeanPostProcessor"></bean>
Procesador personalizado:
package com.ejemplo.procesador;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import com.ejemplo.modelo.Mascota;
/**
* Procesador personalizado para beans
*/
public class MascotaBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Mascota) {
// Mostrar propiedades originales
Mascota mascota = (Mascota) bean;
mascota.saludar();
return bean;
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Mascota) {
// Modificar valores de propiedades y devolver
Mascota mascota = (Mascota) bean;
mascota.setNombre("Canino modificado");
mascota.setEdad(5);
return mascota;
}
return bean;
}
}
Controlador para probar:
package com.ejemplo.controlador;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.ejemplo.modelo.Mascota;
@Controller
public class ControladorPrincipal {
// Acceder a la página principal
@RequestMapping(value="inicio")
public String inicio(HttpServletRequest request){
/**
* Al acceder a inicio, obtener del contenedor la mascota ya procesada
* e imprimir su información
*/
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(servletContext);
Mascota m = (Mascota) ac.getBean("mascota");
m.saludar();
return "inicio";
}
}
Observación de resultados:
Al iniciar el contenedor, se muestra:
Implementación en el código fuente de Spring:
En el método de inicialización de bean: AbstractAutowireCapableBeanFactory.java
/**
* Inicialización del bean
*/
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
// Se omite código no relevante
Object wrappedBean = bean;
// Antes de la inicialización
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// Invocar métodos de inicialización
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Fallo en la invocación del método de inicialización", ex);
}
// Después de la inicialización
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
// Invocación del método postProcessBeforeInitialization
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
// Invocación del método personalizado postProcessBeforeInitialization
Object current = beanProcessor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
// Invocación del método postProcessAfterInitialization
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
// Invocación del método personalizado postProcessAfterInitialization
Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}