Saltar al contenido principal

Contenedores IoC y Configuración de Beans con XML

En los documentos anteriores exploramos los Principios de Diseño (SOLID, IoC, DIP) y la Arquitectura en Capas (Model-Repository-Service). Ahora profundizaremos en el mecanismo técnico que hace posible este ensamblaje: el Contenedor IoC de Spring y la definición declarativa de Spring Beans mediante archivos XML.

En esta guía compararemos cómo funcionaban las aplicaciones Java antes de Spring, analizaremos las diferencias entre BeanFactory y ApplicationContext, y construiremos paso a paso una aplicación completa conectada mediante XML sin utilizar ninguna anotación.


1. Funcionamiento de Proyectos Antes de Spring

En aplicaciones Java tradicionales (Java SE o Servlets puros), el desarrollador era responsable de instanciar y ensamblar manualmente todas las capas del sistema utilizando el operador new.

Problemas del Enfoque Tradicional

Considérese el código de un servicio sin Spring:

src/main/java/com/example/service/EstudianteServiceImpl.java
package com.example.service;

import com.example.repository.EstudianteRepositoryInMemory;

public class EstudianteServiceImpl {

// Acoplamiento fuerte a la clase concreta de bajo nivel
private EstudianteRepositoryInMemory repositorio;

public EstudianteServiceImpl() {
// La clase controla directamente la instanciación de su dependencia
this.repositorio = new EstudianteRepositoryInMemory();
}
}
  • Alto Acoplamiento: Si se desea cambiar EstudianteRepositoryInMemory por EstudianteRepositoryDatabase, es necesario modificar la clase EstudianteServiceImpl y recompilar todo el proyecto.
  • Imposibilidad de Realizar Pruebas Unitarias: No se pueden pasar objetos simulados (mocks) del repositorio para probar la lógica de negocio de forma aislada.

2. El Contenedor IoC de Spring (IoC Container)

Spring soluciona este problema introduciendo el Contenedor IoC (Inversion of Control Container). El contenedor es el motor central del framework encargado de:

  1. Leer los metadatos de configuración declarados en el archivo XML (applicationContext.xml).
  2. Instanciar las clases POJO como Spring Beans.
  3. Inyectar las dependencias requeridas entre ellos.
  4. Gestionar su ciclo de vida y alcances (scopes).

Tipos de Contenedores IoC en Spring

Spring proporciona dos interfaces principales que representan el contenedor IoC:

Contenedor IoCInterfaz JavaCaracterísticas PrincipalesRecomendación de Uso
Bean Factoryorg.springframework.beans.factory.BeanFactoryEl contenedor más básico. Proporciona soporte fundamental para DI y lazy loading (crea los beans solo cuando se solicitan).Reservado para dispositivos con memoria extremadamente limitada.
Application Contextorg.springframework.context.ApplicationContextExtiende de BeanFactory. Agrega integración con aplicaciones web, eventos, internacionalización (I18N) y eager loading por defecto.Opción estándar recomendada para todas las aplicaciones empresariales.

3. ¿Qué es un Spring Bean?

Un Spring Bean es cualquier objeto cuya instanciación, ensamblaje y ciclo de vida son administrados completamente por el Contenedor IoC de Spring, el cual se ejecuta dentro del proceso de la Máquina Virtual de Java (JVM).

A diferencia de las clases instanciadas de forma manual con new, los Spring Beans coexisten dentro del contenedor como componentes vivos (representados orgánicamente como los beans o frijoles de Spring). El contenedor IoC en la JVM se encarga de conectar e inyectar el Bean de Servicio con el Bean de Repositorio mediante Inyección de Dependencias (DI).

Esquema Abstracto de Frijoles Spring en el Contenedor IoC dentro de la JVM

Formas de Registrar un Bean en XML

En este módulo, el registro de beans se realiza exclusivamente en el archivo applicationContext.xml utilizando la etiqueta <bean>:

src/main/resources/applicationContext.xml
<?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.xsd">

<!-- Definición básica de un Bean -->
<bean id="miBean" class="com.example.MiClase" />

</beans>
  • id: Identificador único del bean dentro del contenedor IoC (repoBean, serviceBean, servletBean).
  • class: Nombre completamente cualificado (fully qualified name) de la clase Java a instanciar.

4. Inyección de Dependencias con XML en la Arquitectura por Capas

Retomando la estructura por capas (Model - Repository - Service) definida en el documento anterior, veremos cómo implementar e inyectar las dependencias mediante XML de dos formas: Inyección por Constructor e Inyección por Setter.

A. Capa de Modelo (Model POJO)

src/main/java/com/example/model/Estudiante.java
package com.example.model;

public class Estudiante {

private String id;
private String nombre;
private String correo;

public Estudiante() {
}

public Estudiante(String id, String nombre, String correo) {
this.id = id;
this.nombre = nombre;
this.correo = correo;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getNombre() {
return nombre;
}

public void setNombre(String nombre) {
this.nombre = nombre;
}

public String getCorreo() {
return correo;
}

public void setCorreo(String correo) {
this.correo = correo;
}
}

B. Capa de Repositorio (Repository)

Definimos la interfaz e implementación del repositorio:

src/main/java/com/example/repository/EstudianteRepository.java
package com.example.repository;

import com.example.model.Estudiante;
import java.util.List;

public interface EstudianteRepository {
List<Estudiante> obtenerTodos();
void guardar(Estudiante estudiante);
}
src/main/java/com/example/repository/EstudianteRepositoryInMemory.java
package com.example.repository;

import com.example.model.Estudiante;
import java.util.ArrayList;
import java.util.List;

public class EstudianteRepositoryInMemory implements EstudianteRepository {

private final List<Estudiante> estudiantes = new ArrayList<>();

public EstudianteRepositoryInMemory() {
estudiantes.add(new Estudiante("1", "Ana Gómez", "ana@icesi.edu.co"));
estudiantes.add(new Estudiante("2", "Carlos Pérez", "carlos@icesi.edu.co"));
}

@Override
public List<Estudiante> obtenerTodos() {
return new ArrayList<>(this.estudiantes);
}

@Override
public void guardar(Estudiante estudiante) {
this.estudiantes.add(estudiante);
}
}

C. Capa de Servicio con Inyección por Constructor (Constructor Injection)

La clase de servicio recibe el repositorio a través de su constructor:

src/main/java/com/example/service/EstudianteServiceImpl.java
package com.example.service;

import com.example.model.Estudiante;
import com.example.repository.EstudianteRepository;
import java.util.List;

public class EstudianteServiceImpl implements EstudianteService {

private final EstudianteRepository estudianteRepository;

// Dependencia requerida inyectada por constructor
public EstudianteServiceImpl(EstudianteRepository estudianteRepository) {
this.estudianteRepository = estudianteRepository;
}

@Override
public List<Estudiante> listarEstudiantes() {
return this.estudianteRepository.obtenerTodos();
}

@Override
public void registrarEstudiante(Estudiante estudiante) {
if (estudiante.getCorreo() == null || !estudiante.getCorreo().contains("@")) {
throw new IllegalArgumentException("El correo electrónico no es válido");
}
this.estudianteRepository.guardar(estudiante);
}
}

Declaración en applicationContext.xml:

src/main/resources/applicationContext.xml
<!-- 1. Bean del Repositorio -->
<bean id="estudianteRepositoryBean"
class="com.example.repository.EstudianteRepositoryInMemory" />

<!-- 2. Bean del Servicio usando Inyección por Constructor -->
<bean id="estudianteServiceBean"
class="com.example.service.EstudianteServiceImpl">
<constructor-arg ref="estudianteRepositoryBean" />
</bean>

D. Capa de Servicio con Inyección por Setter (Setter Injection)

Alternativamente, la inyección se puede realizar definiendo métodos setter:

src/main/java/com/example/service/EstudianteServiceSetterImpl.java
package com.example.service;

import com.example.model.Estudiante;
import com.example.repository.EstudianteRepository;
import java.util.List;

public class EstudianteServiceSetterImpl implements EstudianteService {

private EstudianteRepository estudianteRepository;

public EstudianteServiceSetterImpl() {
}

// Setter para inyección de dependencia
public void setEstudianteRepository(EstudianteRepository estudianteRepository) {
this.estudianteRepository = estudianteRepository;
}

@Override
public List<Estudiante> listarEstudiantes() {
return this.estudianteRepository.obtenerTodos();
}

@Override
public void registrarEstudiante(Estudiante estudiante) {
this.estudianteRepository.guardar(estudiante);
}
}

Declaración en applicationContext.xml:

src/main/resources/applicationContext.xml
<!-- Inyección por Setter usando la propiedad 'estudianteRepository' -->
<bean id="estudianteServiceSetterBean"
class="com.example.service.EstudianteServiceSetterImpl">
<property name="estudianteRepository" ref="estudianteRepositoryBean" />
</bean>
Diferencia clave entre constructor-arg y property
  • <constructor-arg ref="..." />: Se utiliza cuando la clase tiene un constructor que recibe dependencias. Garantiza inmutabilidad.
  • <property name="..." ref="..." />: Utiliza el método setter correspondiente (setEstudianteRepository). Requiere un constructor por defecto sin argumentos.

Cuestionario de Autoevaluación

Cargando cuestionario...