Saltar al contenido principal

Capítulo 2: Inyección de Dependencias (DI)

Introducción​

En el Capítulo 1, usted entendió POR QUÉ existe IoC. Ahora, aprenderá CÓMO Spring inyecta las dependencias.

Lo que va a aprender​

  1. Los 3 tipos de inyección de dependencias
  2. Por qué se prefiere la inyección por constructor
  3. Cómo usar @Autowired, @Inject, @Resource
  4. Gestionar dependencias opcionales

Duración: 45 minutos


Los 3 Tipos de Inyección​

Spring ofrece 3 formas de inyectar dependencias:

  1. Inyección por Constructor ✅ (Recomendada)
  2. Inyección por Setter
  3. Inyección por Campo ⚠️ (No recomendada)

Veamos cada una en detalle.


1. Inyección por Constructor ✅​

Sintaxis​

@Service
public class OrderService {

// Dependencias declaradas como 'final'
private final EmailService emailService;
private final PaymentService paymentService;

// Inyección vía el constructor
public OrderService(EmailService emailService,
PaymentService paymentService) {
this.emailService = emailService;
this.paymentService = paymentService;
}
}

¿Cómo Funciona?​

Al iniciar la aplicación:

1. Spring encuentra OrderService
↓
2. Spring ve el constructor con 2 parámetros
↓
3. Spring crea EmailService
↓
4. Spring crea PaymentService
↓
5. Spring crea OrderService pasando las 2 dependencias
↓
6. ¡OrderService está listo!

Ventajas de la Inyección por Constructor​

Ventaja 1: Inmutabilidad​

private final EmailService emailService;
// ^^^^^
// ¡Imposible de modificar después de la creación!

¿Por qué es bueno?

  • ✅ Thread-safe (sin problemas de concurrencia)
  • ✅ Predecible (no puede cambiar)
  • ✅ Menos bugs

Ventaja 2: Dependencias Obligatorias​

public OrderService(EmailService emailService) {
this.emailService = emailService;
}

Si Spring no encuentra EmailService:

❌ ¡La aplicación se niega a iniciar!
❌ Error claro al inicio

¡Esto es BUENO! Descubre el problema de inmediato, ¡no en producción!


Ventaja 3: Fácil de Probar​

@Test
public void testOrderService() {
// Crear fakes
EmailService fakeEmail = new FakeEmailService();
PaymentService fakePayment = new FakePaymentService();

// Inyectar fácilmente vía el constructor
OrderService service = new OrderService(fakeEmail, fakePayment);

// Probar
service.processOrder(new Order());
}

¡No necesita Spring para probar!


Ventaja 4: No Necesita @Autowired​

Desde Spring 4.3, @Autowired es opcional en los constructores:

@Service
public class OrderService {
private final EmailService emailService;

// ¡No necesita @Autowired!
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

¡Spring detecta automáticamente el constructor!


Resumen: Inyección por Constructor​

✅ Dependencias inmutables (final)
✅ Dependencias obligatorias
✅ Fácil de probar
✅ No necesita @Autowired
✅ Recomendado por Spring

👉 ¡UTILICE SIEMPRE LA INYECCIÓN POR CONSTRUCTOR!

2. Inyección por Setter​

Sintaxis​

@Service
public class OrderService {

// Dependencia NO final
private EmailService emailService;

// Inyección vía un setter
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}

¿Cómo Funciona?​

1. Spring crea OrderService (constructor vacío)
↓
2. Spring crea EmailService
↓
3. Spring llama a setEmailService(emailService)
↓
4. OrderService está listo

¿Cuándo Usar?​

Caso de uso: Dependencias opcionales

@Service
public class OrderService {

private EmailService emailService;
private SmsService smsService; // Opcional

@Autowired(required = false) // ← ¡Opcional!
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}

public void notifyCustomer(String message) {
// Email siempre enviado
emailService.send(message);

// SMS solo si está disponible
if (smsService != null) {
smsService.send(message);
}
}
}

Desventajas​

❌ Dependencias mutables (no 'final')
❌ Puede ser null (riesgo de NullPointerException)
❌ Objeto creado en un estado incompleto
❌ Más difícil de probar

¡Utilice solo para dependencias opcionales!


3. Inyección por Campo ⚠️​

Sintaxis​

@Service
public class OrderService {

// Inyección directa en el campo
@Autowired
private EmailService emailService;

@Autowired
private PaymentService paymentService;
}

¿Cómo Funciona?​

1. Spring crea OrderService
↓
2. Spring usa REFLEXIÓN para acceder a los campos privados
↓
3. Spring inyecta las dependencias directamente
↓
4. OrderService está listo

¿Por Qué No Se Recomienda?​

Problema 1: Imposible Hacer 'final'​

@Autowired
private EmailService emailService; // ❌ No se puede 'final'

Problema 2: Difícil de Probar​

@Test
public void testOrderService() {
OrderService service = new OrderService();

// ❌ ¿Cómo inyectar las dependencias?
// ¡Los campos son privados!

// Hay que usar reflexión... ¡complicado!
}

Problema 3: Dependencias Ocultas​

@Service
public class OrderService {
@Autowired
private Dep1 dep1;
@Autowired
private Dep2 dep2;
@Autowired
private Dep3 dep3;
@Autowired
private Dep4 dep4;
@Autowired
private Dep5 dep5;
// ... ¡10 dependencias!
}

Problema: ¡No se ven fácilmente todas las dependencias!

Con constructor:

public OrderService(Dep1 d1, Dep2 d2, Dep3 d3, Dep4 d4, Dep5 d5) {
// ❌ ¡WOW! ¡Demasiadas dependencias!
// ➡️ Señal de que la clase hace demasiadas cosas
}

Resumen: Inyección por Campo​

❌ No es inmutable
❌ Difícil de probar
❌ Oculta problemas de diseño
❌ Usa reflexión (más lento)

👉 ¡EVITE LA INYECCIÓN POR CAMPO!

Comparación de los 3 Tipos​

CriterioConstructorSetterCampo
Inmutabilidad (final)✅ Sí❌ No❌ No
Dependencias obligatorias✅ Sí❌ No⚠️ No claro
Fácil de probar✅ Sí⚠️ Regular❌ No
Legibilidad✅ Clara✅ Clara⚠️ Oculta
¿@Autowired necesario?❌ No✅ Sí✅ Sí
Recomendación✅ UTILICE⚠️ Opcional❌ EVITE

Anotaciones de Inyección​

Spring soporta 3 anotaciones para la inyección:

1. @Autowired (Spring)​

@Service
public class OrderService {

private final EmailService emailService;

@Autowired // Anotación Spring
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

Origen: Spring Framework
Uso: La más común


2. @Inject (Java Standard)​

import jakarta.inject.Inject;

@Service
public class OrderService {

private final EmailService emailService;

@Inject // Anotación Jakarta (Java estándar)
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

Origen: Jakarta EE (anteriormente Java EE)
Uso: Si desea código portable (no ligado a Spring)


3. @Resource (Java Standard)​

import jakarta.annotation.Resource;

@Service
public class OrderService {

@Resource(name = "emailService") // Inyección por nombre
private EmailService emailService;
}

Origen: Jakarta EE
Diferencia: Inyecta por nombre en vez de por tipo


¿Cuál Elegir?​

Recomendación: @Autowired (o ninguna para constructor)

¿Por qué?
✅ Estándar de Spring
✅ Opcional en constructor
✅ Más opciones (required, @Qualifier)
✅ Mejor documentada

Gestión de Múltiples Implementaciones​

Problema​

public interface EmailService {
void send(String to, String message);
}

@Service
public class GmailService implements EmailService { ... }

@Service
public class SendGridService implements EmailService { ... }

// ❌ ¡Spring no sabe cuál elegir!

Solución 1: @Primary​

@Service
@Primary // ← Esta implementación por defecto
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Enviando vía Gmail a " + to);
}
}

@Service
public class SendGridService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Enviando vía SendGrid a " + to);
}
}

¡Spring usará GmailService por defecto!


Solución 2: @Qualifier​

@Service
public class OrderService {

private final EmailService emailService;

public OrderService(@Qualifier("sendGridService") EmailService emailService) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Nombre específico del bean
this.emailService = emailService;
}
}

¡Spring usará específicamente SendGridService!


Solución 3: Nomenclatura Explícita​

@Service("gmailEmailService")  // ← Nombre explícito
public class GmailService implements EmailService { ... }

@Service("sendGridEmailService") // ← Nombre explícito
public class SendGridService implements EmailService { ... }

// Uso
public OrderService(@Qualifier("sendGridEmailService") EmailService service) {
this.emailService = service;
}

Dependencias Opcionales​

Con @Autowired(required = false)​

@Service
public class NotificationService {

private final EmailService emailService;
private SmsService smsService; // Opcional

// Obligatoria
public NotificationService(EmailService emailService) {
this.emailService = emailService;
}

// Opcional
@Autowired(required = false)
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}

public void notify(String message) {
emailService.send(message); // Siempre

if (smsService != null) {
smsService.send(message); // Si está disponible
}
}
}

Con Optional<T>​

import java.util.Optional;

@Service
public class NotificationService {

private final EmailService emailService;
private final Optional<SmsService> smsService;

public NotificationService(
EmailService emailService,
Optional<SmsService> smsService // ← Wrapper Optional
) {
this.emailService = emailService;
this.smsService = smsService;
}

public void notify(String message) {
emailService.send(message);

smsService.ifPresent(sms -> sms.send(message));
// ^^^^^^^^^
// Llamado solo si está presente
}
}

Ejemplo Completo​

// Interfaz
public interface PaymentProcessor {
void process(Order order);
}

// Implementación 1
@Service
@Primary // ← Por defecto
public class StripePaymentProcessor implements PaymentProcessor {

@Override
public void process(Order order) {
System.out.println("Pago vía Stripe: " + order.getTotal());
}
}

// Implementación 2
@Service("paypalProcessor")
public class PayPalPaymentProcessor implements PaymentProcessor {

@Override
public void process(Order order) {
System.out.println("Pago vía PayPal: " + order.getTotal());
}
}

// Uso - Opción 1: Usar @Primary
@Service
public class OrderService {

private final PaymentProcessor paymentProcessor;

// Spring inyecta StripePaymentProcessor (@Primary)
public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}

// Uso - Opción 2: Especificar con @Qualifier
@Service
public class SpecialOrderService {

private final PaymentProcessor paymentProcessor;

// Forzar el uso de PayPal
public SpecialOrderService(
@Qualifier("paypalProcessor") PaymentProcessor paymentProcessor
) {
this.paymentProcessor = paymentProcessor;
}
}

Resumen del Capítulo​

Los 3 Tipos de Inyección​

// 1. Constructor ✅ RECOMENDADO
public MyService(Dependency dep) {
this.dep = dep;
}

// 2. Setter ⚠️ Para dependencias opcionales
@Autowired(required = false)
public void setDependency(Dependency dep) {
this.dep = dep;
}

// 3. Campo ❌ A EVITAR
@Autowired
private Dependency dep;

Elección de Implementación​

// Opción 1: @Primary
@Primary
@Service
public class DefaultImpl implements MyInterface { }

// Opción 2: @Qualifier
public MyService(@Qualifier("specificImpl") MyInterface service) { }

Puntos Clave​

✅ Utilice siempre la inyección por constructor
✅ Declare las dependencias como final
✅ @Autowired opcional en constructor
✅ @Primary para implementación por defecto
✅ @Qualifier para elección específica
⚠️ Setter solo para dependencias opcionales
❌ Evite la inyección por campo


Autoevaluación​

  1. ¿Cuáles son los 3 tipos de inyección?
  2. ¿Por qué se prefiere la inyección por constructor?
  3. ¿Cuál es la diferencia entre @Primary y @Qualifier?
  4. ¿Cómo hacer una dependencia opcional?

Próximo Paso​

Ahora sabe CÓMO inyectar dependencias. ¡Pasemos a la creación y gestión de Beans!

➡️ Capítulo 3: Beans y Contexto Spring

⬅️ Capítulo anterior: IoC

⬅️ Volver al Módulo 5