Pular para o conteúdo principal

Capítulo 3: Beans e Contexto Spring

Introdução​

Nos capítulos anteriores, você utilizou @Service, @Repository, @Controller sem realmente entender o que eram. Este capítulo revela o que é um Bean e como o Spring os gerencia.

O que você vai aprender​

  1. O que é um Bean Spring?
  2. As diferentes anotações de estereótipo
  3. Como criar Beans com @Bean e @Configuration
  4. Como nomear Beans
  5. Como escolher entre vários Beans (@Qualifier, @Primary)

Duração: 45 minutos


O que é um Bean?​

Definição Simples​

Um Bean = Um objeto gerenciado pelo Spring

É só isso! Nada mágico.

Analogia​

Imagine o Spring como um hotel:

Hotel Spring
├── Quarto 101: Bean "userService"
├── Quarto 102: Bean "productRepository"
├── Quarto 103: Bean "orderController"
└── Quarto 104: Bean "emailService"

Spring (o hotel):

  • Cria os objetos (quartos)
  • Os nomeia (números dos quartos)
  • Mantém em memória
  • Entrega quando necessário

Você (o cliente):

  • Solicita um Bean: "Quero userService"
  • Spring entrega: "Aqui está, quarto 101!"

Bean vs Objeto Normal​

Objeto Normal (Sem Spring)​

// Você cria
EmailService emailService = new EmailService();

// Você gerencia o ciclo de vida do objeto
emailService.configure();

// Você precisa passar o objeto para todo lugar
OrderService orderService = new OrderService(emailService);

Problemas:

  • ❌ Você precisa criar manualmente
  • ❌ Você precisa gerenciar as dependências
  • ❌ Você precisa passar os objetos para todo lugar

Bean Spring​

@Service  // ← Spring gerencia este objeto
public class EmailService {
// Configuração automática
}

@Service
public class OrderService {
// Spring injeta automaticamente EmailService
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

Vantagens:

  • ✅ Spring cria automaticamente
  • ✅ Spring gerencia as dependências
  • ✅ Spring injeta onde necessário

Criação de Beans: Método 1 - Anotações de Estereótipo​

As 4 Anotações Principais​

O Spring oferece 4 anotações para criar Beans:

@Component  // Componente genérico
@Service // Camada de negócio
@Repository // Camada de dados
@Controller // Camada de apresentação (web)

Hierarquia​

Todas herdam de @Component!


1. @Component (Genérico)​

Uso: Para qualquer componente que não se encaixa nas outras categorias.

@Component
public class PriceCalculator {

public double calculateDiscount(double price) {
return price * 0.9;
}
}

Quando usar?

  • Classes utilitárias
  • Componentes que não são Service, Repository ou Controller

2. @Service (Lógica de Negócio)​

Uso: Para a camada de negócio (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 negócio
orderRepository.save(order);
emailService.sendConfirmation(order);
}
}

Quando usar?

  • Classes que contêm lógica de negócio
  • Orquestração de múltiplos repositories
  • Transações

3. @Repository (Acesso a Dados)​

Uso: Para a camada de acesso a dados.

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
}

Vantagens específicas:

  • ✅ Tradução automática de exceções SQL para exceções Spring
  • ✅ Indicação clara do papel (acesso a dados)

Quando usar?

  • Interfaces JPA Repository
  • Classes DAO (Data Access Object)

4. @Controller e @RestController (Web)​

Uso: Para a camada 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();
}
}

Diferença:

  • @Controller → Retorna views (HTML)
  • @RestController → Retorna JSON (API REST)

Resumo: Quando Usar Qual Anotação?​

AnotaçãoCamadaUso
@ComponentGenéricoClasses utilitárias, outros componentes
@ServiceNegócioLógica de negócio, orquestração
@RepositoryDadosAcesso a banco de dados
@ControllerWebPáginas web (HTML)
@RestControllerWebAPI REST (JSON)

Regra de ouro: Use a anotação mais específica!


Criação de Beans: Método 2 - @Bean e @Configuration​

Por que Outro Método?​

Problema: Você não pode adicionar @Service em classes que não controla.

Exemplos:

  • Classes de bibliotecas externas
  • Classes Java padrão (ObjectMapper, RestTemplate, etc.)

Solução: @Bean e @Configuration


@Configuration​

Classe de configuração Spring:

@Configuration
public class AppConfig {

// Métodos @Bean aqui
}

@Configuration indica ao Spring: "Esta classe contém definições de Beans"


@Bean​

Criar um Bean manualmente:

@Configuration
public class AppConfig {

@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
return mapper;
}
}

O que acontece:

1. Spring escaneia AppConfig
↓
2. Spring encontra o método objectMapper() anotado com @Bean
↓
3. Spring chama objectMapper()
↓
4. Spring mantém o resultado como Bean chamado "objectMapper"
↓
5. Você pode injetar ObjectMapper em qualquer lugar!

Exemplo Completo​

@Configuration
public class EmailConfig {

// Bean 1: Configuração de emails
@Bean
public EmailSettings emailSettings() {
EmailSettings settings = new EmailSettings();
settings.setHost("smtp.gmail.com");
settings.setPort(587);
return settings;
}

// Bean 2: Serviço de envio (depende do Bean 1)
@Bean
public EmailSender emailSender(EmailSettings settings) {
// ^^^^^^^^^^^^^^^^^^^^^^
// Spring injeta automaticamente!
return new EmailSender(settings);
}
}

Uso:

@Service
public class OrderService {

private final EmailSender emailSender;

// Spring injeta o Bean criado em EmailConfig
public OrderService(EmailSender emailSender) {
this.emailSender = emailSender;
}
}

@Bean vs @Service​

Aspecto@Service@Bean
Sobre o quê?ClasseMétodo
Onde?Em qualquer lugarEm @Configuration
ControleClasse suaQualquer classe
UsoClasses de negócioConfiguração, classes externas

Exemplo:

// ✅ Sua classe
@Service
public class MyService { }

// ✅ Classe externa
@Configuration
public class Config {
@Bean
public ExternalLibraryClass externalClass() {
return new ExternalLibraryClass();
}
}

Nomeação de Beans​

Nome Padrão​

Com @Service, @Component, etc.:

@Service
public class UserService { }
// ^^^^^^^^^^^
// Nome do Bean: "userService" (camelCase)

Com @Bean:

@Bean
public EmailSender emailSender() {
// ^^^^^^^^^^^^
// Nome do Bean: "emailSender"
return new EmailSender();
}

Regra: Primeira letra minúscula (camelCase)


Nome Personalizado​

Com @Service:

@Service("myCustomName")
public class UserService { }
// Bean nomeado: "myCustomName"

Com @Bean:

@Bean(name = "gmailSender")
public EmailSender emailSender() {
return new GmailEmailSender();
}
// Bean nomeado: "gmailSender"

Gerenciando Múltiplas Implementações​

Problema​

public interface PaymentProcessor {
void process(Payment payment);
}

@Service
public class StripePayment implements PaymentProcessor { }

@Service
public class PayPalPayment implements PaymentProcessor { }

// ❌ Spring não sabe qual injetar!
@Service
public class OrderService {
private final PaymentProcessor processor;

public OrderService(PaymentProcessor processor) {
// Erro ao iniciar!
}
}

Solução 1: @Primary​

Marcar uma implementação como padrão:

@Service
@Primary // ← Esta implementação é a padrão
public class StripePayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Pagamento via Stripe");
}
}

@Service
public class PayPalPayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Pagamento via PayPal");
}
}

Resultado:

@Service
public class OrderService {
private final PaymentProcessor processor;

// Spring injeta automaticamente StripePayment (@Primary)
public OrderService(PaymentProcessor processor) {
this.processor = processor;
}
}

Solução 2: @Qualifier​

Escolher uma implementação específica:

@Service("stripePayment")
public class StripePayment implements PaymentProcessor { }

@Service("paypalPayment")
public class PayPalPayment implements PaymentProcessor { }

Injeção com escolha:

@Service
public class OrderService {
private final PaymentProcessor processor;

public OrderService(@Qualifier("paypalPayment") PaymentProcessor processor) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Escolhe explicitamente PayPal
this.processor = processor;
}
}

Solução 3: Injetar Todos os Beans​

Se você precisa de todas as implementações:

@Service
public class PaymentOrchestrator {

private final List<PaymentProcessor> allProcessors;

// Spring injeta uma lista com TODOS os PaymentProcessor
public PaymentOrchestrator(List<PaymentProcessor> allProcessors) {
this.allProcessors = allProcessors;
}

public void processWithAll(Payment payment) {
for (PaymentProcessor processor : allProcessors) {
processor.process(payment);
}
}
}

O Contexto Spring (ApplicationContext)​

O que é o Contexto?​

O Contexto Spring = O container que contém todos os Beans


Acessando o Contexto (Raramente Necessário)​

Normalmente, o Spring injeta automaticamente. Mas se precisar:

@Service
public class MyService {

private final ApplicationContext context;

// Injetar o próprio contexto
public MyService(ApplicationContext context) {
this.context = context;
}

public void doSomething() {
// Recuperar um Bean manualmente
UserService userService = context.getBean(UserService.class);

// Recuperar um Bean pelo nome
EmailSender sender = context.getBean("emailSender", EmailSender.class);

// Listar todos os Beans de um tipo
Map<String, PaymentProcessor> processors =
context.getBeansOfType(PaymentProcessor.class);
}
}

⚠️ Evite fazer isso! É um anti-pattern. Use a injeção normal.


Ciclo de Vida dos Beans​

Etapas​

1. Instanciação
↓
2. Injeção das dependências
↓
3. Inicialização (@PostConstruct)
↓
4. Bean pronto (utilizável)
↓
5. Destruição (@PreDestroy)

Hooks de Ciclo de Vida​

Executar código após a injeção:

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

@Service
public class DatabaseService {

private final DataSource dataSource;

public DatabaseService(DataSource dataSource) {
this.dataSource = dataSource;
// ❌ dataSource ainda não está completamente inicializado
}

@PostConstruct // ← Chamado APÓS a injeção
public void init() {
System.out.println("DatabaseService inicializado");
// ✅ Tudo pronto, pode fazer inicializações
connectToDatabase();
}

@PreDestroy // ← Chamado ANTES da destruição
public void cleanup() {
System.out.println("DatabaseService destruído");
// Fechar conexões, liberar recursos
disconnectFromDatabase();
}
}

Ordem de execução:

1. new DatabaseService(dataSource)  ← Construtor
2. init() ← @PostConstruct
3. [Bean utilizado durante a aplicação]
4. cleanup() ← @PreDestroy (ao encerrar)

Exemplo Completo​

// Interface
public interface NotificationService {
void send(String message);
}

// Implementação 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 destruído");
}
}

// Implementação 2
@Service("smsNotification")
@Primary // ← Padrão
public class SmsNotification implements NotificationService {

@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}

// Configuração
@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 // Os 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 com escolha
@Service
public class SpecialAlertService {

private final NotificationService notification;

public SpecialAlertService(
@Qualifier("emailNotification") NotificationService notification
) {
this.notification = notification;
}
}

Resumo do Capítulo​

O que é um Bean?​

Bean = Objeto gerenciado pelo Spring

Criar Beans​

Método 1: Anotações de estereótipo

@Component  // Genérico
@Service // Negócio
@Repository // Dados
@Controller // Web

Método 2: @Bean em @Configuration

@Configuration
public class Config {
@Bean
public MyClass myBean() {
return new MyClass();
}
}

Gerenciar Múltiplas Implementações​

@Primary              // Implementação padrão
@Qualifier("name") // Escolher especificamente
List<Interface> // Injetar todas

Ciclo de Vida​

@PostConstruct   // Após injeção
@PreDestroy // Antes da destruição

Pontos-Chave​

✅ Bean = Objeto gerenciado pelo Spring
✅ @Service, @Repository, @Controller herdam de @Component
✅ @Bean para classes externas ou configuração complexa
✅ @Primary para implementação padrão
✅ @Qualifier para escolha específica
✅ @PostConstruct para inicialização
✅ @PreDestroy para limpeza


Autoavaliação​

  1. Qual a diferença entre um Bean e um objeto normal?
  2. Quando usar @Service vs @Component?
  3. Qual a diferença entre @Service e @Bean?
  4. Como gerenciar múltiplas implementações de uma interface?
  5. Para que serve @PostConstruct?

Próximo Passo​

Agora você entende o que são Beans. Vamos avançar para Escopos para controlar seu ciclo de vida!

➡️ Capítulo 4: Escopos e Configuração

⬅️ Capítulo anterior: Injeção de Dependências

⬅️ Voltar ao Módulo 5