Procesadores Post-Instancia en Spring: BeanPostProcessor

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;
}

Etiquetas: Spring BeanPostProcessor contenedor IoC ciclo de vida de beans procesadores de Spring

Publicado el 6-2 18:30