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
- Los 3 tipos de inyección de dependencias
- Por qué se prefiere la inyección por constructor
- Cómo usar
@Autowired,@Inject,@Resource - Gestionar dependencias opcionales
Duración: 45 minutos
Los 3 Tipos de Inyección
Spring ofrece 3 formas de inyectar dependencias:
- Inyección por Constructor ✅ (Recomendada)
- Inyección por Setter
- 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
| Criterio | Constructor | Setter | Campo |
|---|---|---|---|
| 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
- ¿Cuáles son los 3 tipos de inyección?
- ¿Por qué se prefiere la inyección por constructor?
- ¿Cuál es la diferencia entre @Primary y @Qualifier?
- ¿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!