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
- O que é um Bean Spring?
- As diferentes anotações de estereótipo
- Como criar Beans com @Bean e @Configuration
- Como nomear Beans
- 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ção | Camada | Uso |
|---|---|---|
@Component | Genérico | Classes utilitárias, outros componentes |
@Service | Negócio | Lógica de negócio, orquestração |
@Repository | Dados | Acesso a banco de dados |
@Controller | Web | Páginas web (HTML) |
@RestController | Web | API 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ê? | Classe | Método |
| Onde? | Em qualquer lugar | Em @Configuration |
| Controle | Classe sua | Qualquer classe |
| Uso | Classes de negócio | Configuraçã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
- Qual a diferença entre um Bean e um objeto normal?
- Quando usar @Service vs @Component?
- Qual a diferença entre @Service e @Bean?
- Como gerenciar múltiplas implementações de uma interface?
- 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