Saltar al contenido principal

Spring IoC Container

Considero que toda la filosofía de Spring está construida sobre cuatro conceptos centrales:

  • IoC Container
  • Beans
  • Annotations
  • Dependency Injection
Scroll to zoom • Drag corner to resize

Es difícil explicar uno sin hacer referencia implícita a los otros, lo que puede hacer que entender los aspectos fundamentales del framework sea bastante desalentador para los recién llegados.

Meme 7e85e1970f1b2ae65f0ecf478400730e

En este documento, vamos a intentar desentrañar esta red interconectada, una pieza a la vez.

Spring IoC Container

Imaginá que sos chef. En lugar de ir corriendo a la granja por los huevos y al molino por la harina, simplemente gritás "¡Necesito ingredientes!" y aparecen en tu mesada, listos para usar. Ese es el IoC Container de Spring, tu asistente personal para objetos Java.

Funciona así:

  1. Component Scan: Spring escanea tu código en busca de clases especiales marcadas con anotaciones como @Component, @Service o @Repository.
  2. Bean Creation: Crea instancias de estas clases, llamadas "beans", y maneja todo su ciclo de vida.
  3. Dependency Injection: Cuando uno de tus beans necesita otro, Spring automáticamente se lo provee.

Beans

Un bean es un objeto manejado por el IoC Container de Spring (Inversión de Control).

  • Los beans se definen en la configuración de Spring, ya sea vía:
    • Anotaciones (ej. @Component, @Service, @Repository, @Configuration).
    • Configuración XML (NO lo uses en Spring moderno).
    • Configuración basada en Java (métodos @Bean en clases @Configuration).
  • Singleton por default: Por defecto, un bean de Spring es un singleton (una instancia por contenedor). Esto se puede personalizar con scopes como @Scope("prototype").
  • Los beans pueden inyectarse entre sí. Esto promueve el desacoplamiento y la testeabilidad.
  • El contenedor controla el ciclo de vida de un bean, desde la instanciación hasta la destrucción. Podés definir hooks personalizados con anotaciones como @PostConstruct y @PreDestroy.

Annotations

Las anotaciones son la forma de Spring de permitirte etiquetar tu código con instrucciones como "¡Hey Spring, manejá esta clase!" o "¡Inyectá esa dependencia acá!".

Definición de Beans

AnotaciónSignificadoCaso de Uso
@Component"¡Spring, manejá esta clase!"Beans genéricos
@Service"¡Acá hay lógica de negocio!"Clases de la capa de servicio
@Repository"¡Acá hay interacciones con base de datos!"DAOs/clases de base de datos (agrega traducción de excepciones)
@RestController"¡Endpoint de API!" (Combina @Controller + @ResponseBody)APIs REST

Dependency Injection

AnotaciónSignificadoEjemplo
@Autowired"¡Inyectá un bean acá!"Constructor/field/setter
@Primary"¡Elegime primero!"Resolver conflictos de beans ambiguos
@Qualifier"Inyectá ESTE bean específico"@Qualifier("mysqlDb")

Configuración

AnotaciónSignificadoEjemplo
@Configuration"¡Esta clase configura beans!"Setup de base de datos/librerías de terceros
@Bean"¡Acá hay un bean para manejar!"Métodos que retornan objetos complejos
@Value"¡Inyectá un valor de propiedad!"@Value("${api.key}")

Web/REST

AnotaciónSignificadoEjemplo
@RequestMapping"Mapeá requests a este método"@RequestMapping("/users")
@GetMapping"Manejá requests GET"@GetMapping("/{id}")
@PostMapping"Manejá requests POST"@PostMapping("/create")
@RequestBody"Convertí JSON → objeto Java"createUser(@RequestBody User user)
@PathVariable"Obtené parámetros de URL"@PathVariable Long id

Lombok

AnotaciónPropósitoEjemplo
@Getter / @SetterAuto-generá getters/setters@Getter @Setter private String username;
@ToStringAuto-generá toString()@ToString(exclude = "password")
@EqualsAndHashCodeAuto-generá equals() y hashCode()@EqualsAndHashCode(callSuper = true)
@NoArgsConstructorGenerá constructor sin args@NoArgsConstructor
@AllArgsConstructorGenerá constructor con todos los args@AllArgsConstructor
@RequiredArgsConstructorGenerá constructor con campos final/@NonNull@RequiredArgsConstructor
@DataTodo-en-uno (@Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor)@Data public class User { ... }
@BuilderImplementá patrón BuilderUser.builder().name("Alice").build();
@Slf4jInyectá logger (Logger log)log.info("User created: {}", username);
¡No para proyectos Groovy!

Si estás usando Groovy, no necesitás Lombok. Las transformaciones AST (Abstract Syntax Tree) built-in de Groovy hacen todo lo que hace Lombok, pero lo hacen como ciudadano de primera clase del lenguaje en lugar de un "hack" que se conecta al compilador.

FeatureLombokGroovy AST
Todas las características@Data@Canonical (Combina Equals, HashCode, ToString, etc.)
Inmutabilidad@Value@Immutable
Constructores@AllArgsConstructor@TupleConstructor
Patrón Builder@Builder@Builder
Logging@Slf4j@Slf4j (o @Log)
Delegación@Delegate (Experimental-ish)@Delegate (Sólido y poderoso)
Lazy Loading@Getter(lazy=true)@Lazy
¡No para proyectos Kotlin!

Kotlin fue diseñado desde cero para resolver los problemas que Lombok aborda, pero lo hace como parte de la sintaxis del lenguaje en lugar de depender de trucos de procesamiento de anotaciones. Esto significa código más legible, idiomático con menos "magia" pasando detrás de escena.

Feature de LombokEquivalente en KotlinPor qué es mejor
@Data / @Valuedata classObtenés equals, hashCode, toString y copy() en una línea.
@Getter / @SetterPropiedades (val/var)Los accesores son built-in. user.name llama al getter bajo el capó.
@AllArgsConstructorConstructor PrimarioDefinido directo en el header de la clase. No se necesita magia.
@BuilderParámetros Nombrados y por DefaultUser(name = "Franco", age = 30) es más limpio que un patrón builder.
@Slf4jCompanion object o LibreríasLa mayoría usa private val log = LoggerFactory.getLogger(javaClass) o una librería como kotlin-logging.
@NonNullTipos Nulables (String?)El sistema de tipos de Kotlin maneja null safety en tiempo de compilación, en lugar de chequeos en runtime.

Tips

  1. Preferí constructor injection (private final + Lombok @RequiredArgsConstructor) sobre field injection (@Autowired).

  2. Estructurá tus anotaciones en capas.

    @Repository  // Data layer
    public class UserRepository { ... }

    @Service // Business logic
    public class UserService { ... }

    @RestController // API layer
    public class UserController { ... }
  3. Usá @Configuration + @Bean para conectar dependencias complejas.

    @Configuration
    public class SecurityConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
    }
    }
  4. Beans específicos por ambiente: Usá @Profile para configuraciones dev/staging/prod.

    @Profile("dev")
    @Service
    public class MockPaymentService implements PaymentService { ... }
  5. Evitá la sopa de anotaciones: Mantenete con una anotación específica del rol.

    @Component @Service  // Redundant!
    public class UserService { ... }

Dependency Injection

Le decís a Spring qué necesitás pidiendo la interfaz. Hay varias formas de hacerlo, pero solo una es la correcta.

Constructor Injection (La Forma Correcta)

@RestController
public class CheckoutController {

private final PaymentService paymentService;

// You ask for the "what" (the interface) in your constructor
public CheckoutController(PaymentService paymentService) {
this.paymentService = paymentService;
}

//Controller logic...
}

Este es el estándar de oro. ¿Por qué?

  • No hay Nulls: El objeto ni siquiera puede crearse sin sus dependencias. Te obliga a tener un objeto válido y listo para usar desde el principio.
  • Inmutabilidad: Al declarar el campo final, garantizás que no puede cambiar después. No vas a cambiar accidentalmente tu base de datos por una tostadora en medio de un request.
  • Amigable para Testing: En tus unit tests, no necesitás Spring. Simplemente llamás new CheckoutController(new MockPaymentService()) y listo. Limpio, simple y rápido.

Mejor aún, te podés deshacer del boilerplate. En Java, usarías una librería como Lombok, pero en Kotlin esto es una feature built-in del lenguaje.

@RestController
@RequiredArgsConstructor // <-- Lombok annotation
public class CheckoutController {

private final PaymentService paymentService;

//Controller logic
}

Lombok genera el constructor para todos tus campos final automáticamente. Limpio, conciso e imposible de equivocarse. Así es como se hace.

Setter Injection

Esto es para dependencias opcionales. El problema es que tu objeto puede existir en un estado donde sus dependencias son null. Es menos seguro y hace tu código más difícil de razonar.

@RestController
public class CheckoutController {

private PaymentService paymentService;

@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
//...
}

Field Injection

Se ve limpio, pero es una trampa. Esconde las dependencias, hace tu clase más difícil de testear sin la magia de Spring, y hace imposible crear objetos inmutables.

@RestController
public class CheckoutController {

@Autowired
private PaymentService paymentService;

//...
}

Como nota final sobre este tema, realmente recomiendo el video de CodeAesthetic "Dependency Injection, The Best Pattern". El punto queda clarísimo.