Pollito Blog
November 25, 2024

Hablemos de Java: Beans

Posted on November 25, 2024  •  8 minutes  • 1590 words  • Other languages:  English

Bean… Es uno de esos términos que nosotros, los desarrolladores Java, lanzamos tan seguido que a veces nos olvidamos de detenernos a apreciar su elegancia. Descubramos qué es realmente un Spring bean.

¿Qué es un bean?

En el framework Spring, un bean es simplemente un objeto que gestiona el contenedor IoC (Inversion of Control) de Spring. Es la columna vertebral del mecanismo de inyección de dependencias (DI) de Spring.

Para desglosarlo:

Características clave de un Spring bean

Ejemplo

Acá tenés un bean sencillo:

@Component
public class CoffeeMaker {
    public String brew() {
        return "¡Preparando una taza fresca de Java!";
    }
}

Y otro bean que depende de él:

@Service
public class CoffeeShop {
    private final CoffeeMaker coffeeMaker;

    public CoffeeShop(CoffeeMaker coffeeMaker) {
        this.coffeeMaker = coffeeMaker;
    }

    public void openShop() {
        System.out.println(coffeeMaker.brew());
    }
}

El contenedor de Spring detecta las anotaciones @Component y @Service, crea beans para estas clases y los conecta.

Inyección de dependencias para conectar beans

La inyección de dependencias (DI) es un patrón de diseño donde las dependencias de un objeto son provistas por una fuente externa (el contenedor IoC de Spring) en lugar de que el objeto las cree por sí mismo.

Este es el mejor video que encontré explicando el tema:

En Spring, la DI se usa para conectar beans entre sí, haciendo que las aplicaciones estén poco acopladas y sean más fáciles de testear y mantener.

Métodos de inyección

  1. Inyección en el campo: Usá @Autowired directamente en una variable.

    @Autowired
    private MyService myService;
    
  2. Inyección por constructor (preferida): Las dependencias se proporcionan a través del constructor.

    @Service
    public class MyService {
        private final MyDependency dependency;
    
        public MyService(MyDependency dependency) {
            this.dependency = dependency;
        }
    }
    
  3. Inyección por setter: Las dependencias se inyectan mediante un método setter.

    private MyDependency dependency;
    
    @Autowired
    public void setDependency(MyDependency dependency) {
        this.dependency = dependency;
    }
    

¿Por qué usar inyección de dependencias?

La inyección por constructor es típicamente preferida porque asegura que las dependencias sean inmutables y obligatorias para el funcionamiento del objeto.

¿Por qué singleton por defecto?

¿Cuándo querrías cambiar el comportamiento singleton?

El modelo singleton no siempre es apropiado. Acá van algunas situaciones donde cambiarlo tiene sentido:

¿Cuándo usar singleton?

Spring por defecto usa singleton porque se ajusta al caso de uso más común: servicios compartidos y sin estado. Deberías cambiar el scope cuando el estado o la duración sean críticos para la función del bean (por ej., comportamiento por petición/sesión, datos específicos de una tarea).

Si te encontrás preguntando si necesitás un bean no singleton, siempre preguntate:

El ciclo de vida de un Spring bean

  1. Instanciación.
    • El contenedor IoC de Spring crea una instancia del bean, ya sea mediante su constructor o un método factory.
  2. Inyección de dependencias.
    • Después de la instanciación, el contenedor inyecta las dependencias (ya sea por constructor, setter o inyección en el campo).
  3. Hooks post-inicialización.
    • Los beans pasan por hooks post-inicialización para configuraciones o setups adicionales:
      • Si el bean implementa la interfaz InitializingBean, se llama a su método afterPropertiesSet().
      • Si el bean tiene un método anotado con @PostConstruct, este se ejecuta.
  4. Listo para usar.
    • El bean ya está completamente inicializado, con sus dependencias inyectadas, y listo para usarse.
  5. Destrucción.
    • Cuando la aplicación se apaga, el bean se destruye. Spring provee hooks para limpieza personalizada:
      • Si el bean implementa DisposableBean, se llama a su método destroy().
      • Si el bean tiene un método anotado con @PreDestroy, se ejecuta ese método.

Hooks del ciclo de vida en detalle

  1. Hooks de inicialización

    • Se usan frecuentemente para inicializar recursos, iniciar hilos en segundo plano o realizar tareas de setup.
    • Opciones:
      • @PostConstruct: Un enfoque moderno basado en anotaciones.
      • InitializingBean.afterPropertiesSet(): Enfoque basado en interfaz.
      • Métodos de inicialización personalizados: Declarados con el atributo @Bean(initMethod = "methodName").
    @Component
    public class ExampleBean {
        @PostConstruct
        public void initialize() {
            System.out.println("¡El bean se ha inicializado!");
        }
    }
    
  2. Hooks de destrucción

    • Se usan para liberar recursos, cerrar conexiones o realizar tareas de limpieza.
    • Opciones:
      • @PreDestroy: El enfoque basado en anotaciones recomendado.
      • DisposableBean.destroy(): Enfoque basado en interfaz.
      • Métodos de destrucción personalizados: Declarados con el atributo @Bean(destroyMethod = "methodName").
    @Component
    public class ExampleBean {
        @PreDestroy
        public void cleanup() {
            System.out.println("¡El bean se está destruyendo!");
        }
    }
    

Ciclo de vida completo en código

@Component
public class ExampleBean implements InitializingBean, DisposableBean {
    public ExampleBean() {
        System.out.println("1. Se instancia el bean.");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("2. @PostConstruct: El bean se inicializa.");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("3. afterPropertiesSet(): Lógica de inicialización personalizada.");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("4. @PreDestroy: Limpieza antes de la destrucción.");
    }

    @Override
    public void destroy() {
        System.out.println("5. destroy(): Limpieza final.");
    }
}

Output:

1. Se instancia el bean.
2. @PostConstruct: El bean se inicializa.
3. afterPropertiesSet(): Lógica de inicialización personalizada.
4. @PreDestroy: Limpieza antes de la destrucción.
5. destroy(): Limpieza final.

Ciclo de vida de bean con scopes

¿Cuándo usarías estos hooks?

Spring te da un control muy fino sobre el ciclo de vida de un bean. Si bien hooks como @PostConstruct y @PreDestroy son los más modernos y ampliamente usados, el ciclo de vida es lo suficientemente flexible para incorporar lógica personalizada cuando sea necesario.

Conclusión

Hey, check me out!

You can find me here