Saltar al contenido principal

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​

  1. ¿Qué es un Bean de Spring?
  2. Las diferentes anotaciones de estereotipo
  3. Cómo crear Beans con @Bean y @Configuration
  4. Cómo nombrar los Beans
  5. 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ónCapaUso
@ComponentGenéricoClases utilitarias, otros componentes
@ServiceNegocioLógica de negocio, orquestación
@RepositoryDatosAcceso a base de datos
@ControllerWebPáginas web (HTML)
@RestControllerWebAPI 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é?ClaseMétodo
¿Dónde?En cualquier parteDentro de @Configuration
ControlClase propiaCualquier clase
UsoClases de negocioConfiguració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​

  1. ¿Cuál es la diferencia entre un Bean y un objeto normal?
  2. ¿Cuándo usar @Service vs @Component?
  3. ¿Cuál es la diferencia entre @Service y @Bean?
  4. ¿Cómo gestionar varias implementaciones de una interfaz?
  5. ¿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

⬅️ Capítulo anterior: Inyección de Dependencias

⬅️ Volver al Módulo 5