Capítulo 3: Beans y Contexto Spring
Introducción
En los capítulos anteriores, usted ha utilizado @Service, @Repository, @Controller sin realmente entender qué eran. Este capítulo le revela qué es un Bean y cómo los gestiona Spring.
Lo que va a aprender
- ¿Qué es un Bean de Spring?
- Las diferentes anotaciones de estereotipo
- Cómo crear Beans con @Bean y @Configuration
- Cómo nombrar los Beans
- Cómo elegir entre varios Beans (@Qualifier, @Primary)
Duración: 45 minutos
¿Qué es un Bean?
Definición Simple
Un Bean = Un objeto gestionado por Spring
¡Eso es todo! Nada mágico.
Analogía
Imagine Spring como un hotel:
Hotel Spring
├── Habitación 101: Bean "userService"
├── Habitación 102: Bean "productRepository"
├── Habitación 103: Bean "orderController"
└── Habitación 104: Bean "emailService"
Spring (el hotel):
- Crea los objetos (habitaciones)
- Les da nombre (números de habitación)
- Los mantiene en memoria
- Los entrega cuando se necesitan
Usted (el cliente):
- Solicita un Bean: "Quiero userService"
- Spring se lo da: "¡Aquí está, habitación 101!"
Bean vs Objeto Normal
Objeto Normal (Sin Spring)
// Usted crea
EmailService emailService = new EmailService();
// Usted gestiona la vida del objeto
emailService.configure();
// Usted debe pasarlo por todas partes
OrderService orderService = new OrderService(emailService);
Problemas:
- ❌ Usted debe crear manualmente
- ❌ Usted debe gestionar las dependencias
- ❌ Usted debe pasar los objetos por todas partes
Bean Spring
@Service // ← Spring gestiona este objeto
public class EmailService {
// Configuración automática
}
@Service
public class OrderService {
// Spring inyecta automáticamente EmailService
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Ventajas:
- ✅ Spring crea automáticamente
- ✅ Spring gestiona las dependencias
- ✅ Spring inyecta donde sea necesario
Creación de Beans: Método 1 - Anotaciones de Estereotipo
Las 4 Anotaciones Principales
Spring ofrece 4 anotaciones para crear Beans:
@Component // Componente genérico
@Service // Capa de negocio
@Repository // Capa de datos
@Controller // Capa de presentación (web)
Jerarquía
¡Todas heredan de @Component!
1. @Component (Genérico)
Uso: Para cualquier componente que no encaje en las otras categorías.
@Component
public class PriceCalculator {
public double calculateDiscount(double price) {
return price * 0.9;
}
}
¿Cuándo usarlo?
- Clases utilitarias
- Componentes que no son ni Service, ni Repository, ni Controller
2. @Service (Lógica de Negocio)
Uso: Para la capa de negocio (Service Layer).
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final EmailService emailService;
public OrderService(OrderRepository orderRepository,
EmailService emailService) {
this.orderRepository = orderRepository;
this.emailService = emailService;
}
public void processOrder(Order order) {
// Lógica de negocio
orderRepository.save(order);
emailService.sendConfirmation(order);
}
}
¿Cuándo usarlo?
- Clases que contienen la lógica de negocio
- Orquestación de varios repositories
- Transacciones
3. @Repository (Acceso a Datos)
Uso: Para la capa de acceso a datos.
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
}
Ventajas específicas:
- ✅ Traducción automática de excepciones SQL a excepciones de Spring
- ✅ Indicación clara del rol (acceso a datos)
¿Cuándo usarlo?
- Interfaces JPA Repository
- Clases DAO (Data Access Object)
4. @Controller y @RestController (Web)
Uso: Para la capa web.
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping
public List<Order> getAllOrders() {
return orderService.getAllOrders();
}
}
Diferencia:
@Controller→ Devuelve vistas (HTML)@RestController→ Devuelve JSON (API REST)
Resumen: ¿Cuándo Usar Qué Anotación?
| Anotación | Capa | Uso |
|---|---|---|
@Component | Genérico | Clases utilitarias, otros componentes |
@Service | Negocio | Lógica de negocio, orquestación |
@Repository | Datos | Acceso a base de datos |
@Controller | Web | Páginas web (HTML) |
@RestController | Web | API REST (JSON) |
Regla de oro: ¡Utilice la anotación más específica!
Creación de Beans: Método 2 - @Bean y @Configuration
¿Por Qué Otro Método?
Problema: Usted no puede añadir @Service en clases que no controla.
Ejemplos:
- Clases de bibliotecas externas
- Clases Java estándar (ObjectMapper, RestTemplate, etc.)
Solución: @Bean y @Configuration
@Configuration
Clase de configuración de Spring:
@Configuration
public class AppConfig {
// Métodos @Bean aquí
}
@Configuration indica a Spring: "Esta clase contiene definiciones de Beans"
@Bean
Crear un Bean manualmente:
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
return mapper;
}
}
Lo que sucede:
1. Spring escanea AppConfig
↓
2. Spring encuentra el método objectMapper() anotado con @Bean
↓
3. Spring llama a objectMapper()
↓
4. Spring guarda el resultado como Bean llamado "objectMapper"
↓
5. ¡Usted puede inyectar ObjectMapper en cualquier parte!
Ejemplo Completo
@Configuration
public class EmailConfig {
// Bean 1: Configuración de emails
@Bean
public EmailSettings emailSettings() {
EmailSettings settings = new EmailSettings();
settings.setHost("smtp.gmail.com");
settings.setPort(587);
return settings;
}
// Bean 2: Servicio de envío (depende del Bean 1)
@Bean
public EmailSender emailSender(EmailSettings settings) {
// ^^^^^^^^^^^^^^^^^^^^^^
// ¡Spring lo inyecta automáticamente!
return new EmailSender(settings);
}
}
Uso:
@Service
public class OrderService {
private final EmailSender emailSender;
// Spring inyecta el Bean creado en EmailConfig
public OrderService(EmailSender emailSender) {
this.emailSender = emailSender;
}
}
@Bean vs @Service
| Aspecto | @Service | @Bean |
|---|---|---|
| ¿Sobre qué? | Clase | Método |
| ¿Dónde? | En cualquier parte | Dentro de @Configuration |
| Control | Clase propia | Cualquier clase |
| Uso | Clases de negocio | Configuración, clases externas |
Ejemplo:
// ✅ Su clase
@Service
public class MyService { }
// ✅ Clase externa
@Configuration
public class Config {
@Bean
public ExternalLibraryClass externalClass() {
return new ExternalLibraryClass();
}
}
Nomenclatura de los Beans
Nombre por Defecto
Con @Service, @Component, etc.:
@Service
public class UserService { }
// ^^^^^^^^^^^
// Nombre del Bean: "userService" (camelCase)
Con @Bean:
@Bean
public EmailSender emailSender() {
// ^^^^^^^^^^^^
// Nombre del Bean: "emailSender"
return new EmailSender();
}
Regla: Primera letra en minúscula (camelCase)
Nombre Personalizado
Con @Service:
@Service("myCustomName")
public class UserService { }
// Bean llamado: "myCustomName"
Con @Bean:
@Bean(name = "gmailSender")
public EmailSender emailSender() {
return new GmailEmailSender();
}
// Bean llamado: "gmailSender"
Gestionar Varias Implementaciones
Problema
public interface PaymentProcessor {
void process(Payment payment);
}
@Service
public class StripePayment implements PaymentProcessor { }
@Service
public class PayPalPayment implements PaymentProcessor { }
// ❌ ¡Spring no sabe cuál inyectar!
@Service
public class OrderService {
private final PaymentProcessor processor;
public OrderService(PaymentProcessor processor) {
// ¡Error al iniciar!
}
}
Solución 1: @Primary
Marcar una implementación como la predeterminada:
@Service
@Primary // ← Esta implementación es la predeterminada
public class StripePayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Pago vía Stripe");
}
}
@Service
public class PayPalPayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Pago vía PayPal");
}
}
Resultado:
@Service
public class OrderService {
private final PaymentProcessor processor;
// Spring inyecta automáticamente StripePayment (@Primary)
public OrderService(PaymentProcessor processor) {
this.processor = processor;
}
}
Solución 2: @Qualifier
Elegir una implementación específica:
@Service("stripePayment")
public class StripePayment implements PaymentProcessor { }
@Service("paypalPayment")
public class PayPalPayment implements PaymentProcessor { }
Inyección con elección:
@Service
public class OrderService {
private final PaymentProcessor processor;
public OrderService(@Qualifier("paypalPayment") PaymentProcessor processor) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Elige explícitamente PayPal
this.processor = processor;
}
}
Solución 3: Inyectar Todos los Beans
Si necesita todas las implementaciones:
@Service
public class PaymentOrchestrator {
private final List<PaymentProcessor> allProcessors;
// Spring inyecta una lista con TODOS los PaymentProcessor
public PaymentOrchestrator(List<PaymentProcessor> allProcessors) {
this.allProcessors = allProcessors;
}
public void processWithAll(Payment payment) {
for (PaymentProcessor processor : allProcessors) {
processor.process(payment);
}
}
}
El Contexto Spring (ApplicationContext)
¿Qué es el Contexto?
El Contexto Spring = El contenedor que contiene todos los Beans
Acceder al Contexto (Rara Vez Necesario)
Normalmente, Spring inyecta automáticamente. Pero si es necesario:
@Service
public class MyService {
private final ApplicationContext context;
// Inyectar el contexto en sí mismo
public MyService(ApplicationContext context) {
this.context = context;
}
public void doSomething() {
// Recuperar un Bean manualmente
UserService userService = context.getBean(UserService.class);
// Recuperar un Bean por nombre
EmailSender sender = context.getBean("emailSender", EmailSender.class);
// Listar todos los Beans de un tipo
Map<String, PaymentProcessor> processors =
context.getBeansOfType(PaymentProcessor.class);
}
}
⚠️ ¡Evite hacer esto! Es un anti-pattern. Utilice la inyección normal.
Ciclo de Vida de los Beans
Etapas
1. Instanciación
↓
2. Inyección de dependencias
↓
3. Inicialización (@PostConstruct)
↓
4. Bean listo (utilizable)
↓
5. Destrucción (@PreDestroy)
Hooks de Ciclo de Vida
Ejecutar código después de la inyección:
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Service
public class DatabaseService {
private final DataSource dataSource;
public DatabaseService(DataSource dataSource) {
this.dataSource = dataSource;
// ❌ dataSource aún no está completamente inicializado
}
@PostConstruct // ← Llamado DESPUÉS de la inyección
public void init() {
System.out.println("DatabaseService inicializado");
// ✅ Todo está listo, puede hacer inicializaciones
connectToDatabase();
}
@PreDestroy // ← Llamado ANTES de la destrucción
public void cleanup() {
System.out.println("DatabaseService destruido");
// Cerrar conexiones, liberar recursos
disconnectFromDatabase();
}
}
Orden de ejecución:
1. new DatabaseService(dataSource) ← Constructor
2. init() ← @PostConstruct
3. [Bean utilizado durante la aplicación]
4. cleanup() ← @PreDestroy (al detener)
Ejemplo Completo
// Interfaz
public interface NotificationService {
void send(String message);
}
// Implementación 1
@Service("emailNotification")
public class EmailNotification implements NotificationService {
@PostConstruct
public void init() {
System.out.println("EmailNotification inicializado");
}
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
@PreDestroy
public void cleanup() {
System.out.println("EmailNotification destruido");
}
}
// Implementación 2
@Service("smsNotification")
@Primary // ← Por defecto
public class SmsNotification implements NotificationService {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
// Configuración
@Configuration
public class AppConfig {
@Bean
public NotificationLogger logger() {
return new NotificationLogger();
}
}
// Uso
@Service
public class AlertService {
private final NotificationService notification;
private final List<NotificationService> allNotifications;
public AlertService(
NotificationService notification, // SmsNotification (@Primary)
List<NotificationService> allNotifications // Los 2
) {
this.notification = notification;
this.allNotifications = allNotifications;
}
public void alert(String message) {
// Usa SMS (Primary)
notification.send(message);
}
public void alertAll(String message) {
// Usa todos
allNotifications.forEach(n -> n.send(message));
}
}
// Uso con elección
@Service
public class SpecialAlertService {
private final NotificationService notification;
public SpecialAlertService(
@Qualifier("emailNotification") NotificationService notification
) {
this.notification = notification;
}
}
Resumen del Capítulo
¿Qué es un Bean?
Bean = Objeto gestionado por Spring
Crear Beans
Método 1: Anotaciones de estereotipo
@Component // Genérico
@Service // Negocio
@Repository // Datos
@Controller // Web
Método 2: @Bean en @Configuration
@Configuration
public class Config {
@Bean
public MyClass myBean() {
return new MyClass();
}
}
Gestionar Varias Implementaciones
@Primary // Implementación por defecto
@Qualifier("name") // Elegir específicamente
List<Interface> // Inyectar todas
Ciclo de Vida
@PostConstruct // Después de la inyección
@PreDestroy // Antes de la destrucción
Puntos Clave
✅ Bean = Objeto gestionado por Spring
✅ @Service, @Repository, @Controller heredan de @Component
✅ @Bean para clases externas o configuración compleja
✅ @Primary para implementación por defecto
✅ @Qualifier para elección específica
✅ @PostConstruct para inicialización
✅ @PreDestroy para limpieza
Autoevaluación
- ¿Cuál es la diferencia entre un Bean y un objeto normal?
- ¿Cuándo usar @Service vs @Component?
- ¿Cuál es la diferencia entre @Service y @Bean?
- ¿Cómo gestionar varias implementaciones de una interfaz?
- ¿Para qué sirve @PostConstruct?
Próximo Paso
Ahora entiende qué son los Beans. ¡Pasemos a los Ámbitos para controlar su ciclo de vida!
➡️ Capítulo 4: Ámbitos y Configuración