Capítulo 2: Injeção de Dependências (DI)
Introdução
No Capítulo 1, você entendeu POR QUE o IoC existe. Agora, você vai aprender COMO o Spring injeta as dependências.
O que você vai aprender
- Os 3 tipos de injeção de dependências
- Por que a injeção por construtor é preferida
- Como usar
@Autowired,@Inject,@Resource - Como lidar com dependências opcionais
Duração: 45 minutos
Os 3 Tipos de Injeção
O Spring oferece 3 formas de injetar dependências:
- Injeção por Construtor ✅ (Recomendada)
- Injeção por Setter
- Injeção por Campo ⚠️ (Não recomendada)
Vamos ver cada uma em detalhes.
1. Injeção por Construtor ✅
Sintaxe
@Service
public class OrderService {
// Dependências declaradas como 'final'
private final EmailService emailService;
private final PaymentService paymentService;
// Injeção via construtor
public OrderService(EmailService emailService,
PaymentService paymentService) {
this.emailService = emailService;
this.paymentService = paymentService;
}
}
Como Funciona?
Na inicialização da aplicação:
1. O Spring encontra OrderService
↓
2. O Spring vê o construtor com 2 parâmetros
↓
3. O Spring cria EmailService
↓
4. O Spring cria PaymentService
↓
5. O Spring cria OrderService passando as 2 dependências
↓
6. OrderService está pronto!
Vantagens da Injeção por Construtor
Vantagem 1: Imutabilidade
private final EmailService emailService;
// ^^^^^
// Impossível modificar após a criação!
Por que isso é bom?
- ✅ Thread-safe (sem problemas de concorrência)
- ✅ Previsível (não pode mudar)
- ✅ Menos bugs
Vantagem 2: Dependências Obrigatórias
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
Se o Spring não encontrar EmailService:
❌ A aplicação se recusa a iniciar!
❌ Erro claro na inicialização
Isso é BOM! Você descobre o problema imediatamente, não em produção!
Vantagem 3: Fácil de Testar
@Test
public void testOrderService() {
// Criar fakes
EmailService fakeEmail = new FakeEmailService();
PaymentService fakePayment = new FakePaymentService();
// Injetar facilmente via construtor
OrderService service = new OrderService(fakeEmail, fakePayment);
// Testar
service.processOrder(new Order());
}
Não precisa do Spring para testar!
Vantagem 4: Não Precisa de @Autowired
Desde o Spring 4.3, @Autowired é opcional em construtores:
@Service
public class OrderService {
private final EmailService emailService;
// Não precisa de @Autowired!
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
O Spring detecta automaticamente o construtor!
Resumo: Injeção por Construtor
✅ Dependências imutáveis (final)
✅ Dependências obrigatórias
✅ Fácil de testar
✅ Não precisa de @Autowired
✅ Recomendado pelo Spring
👉 SEMPRE USE INJEÇÃO POR CONSTRUTOR!
2. Injeção por Setter
Sintaxe
@Service
public class OrderService {
// Dependência NÃO final
private EmailService emailService;
// Injeção via setter
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
Como Funciona?
1. O Spring cria OrderService (construtor vazio)
↓
2. O Spring cria EmailService
↓
3. O Spring chama setEmailService(emailService)
↓
4. OrderService está pronto
Quando Usar?
Caso de uso: Dependências opcionais
@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 sempre enviado
emailService.send(message);
// SMS somente se disponível
if (smsService != null) {
smsService.send(message);
}
}
}
Desvantagens
❌ Dependências mutáveis (não é 'final')
❌ Pode ser null (risco de NullPointerException)
❌ Objeto criado em estado incompleto
❌ Mais difícil de testar
Use apenas para dependências opcionais!
3. Injeção por Campo ⚠️
Sintaxe
@Service
public class OrderService {
// Injeção direta no campo
@Autowired
private EmailService emailService;
@Autowired
private PaymentService paymentService;
}
Como Funciona?
1. O Spring cria OrderService
↓
2. O Spring usa REFLEXÃO para acessar os campos privados
↓
3. O Spring injeta as dependências diretamente
↓
4. OrderService está pronto
Por Que Não É Recomendado?
Problema 1: Impossível Tornar 'final'
@Autowired
private EmailService emailService; // ❌ Não pode ser 'final'
Problema 2: Difícil de Testar
@Test
public void testOrderService() {
OrderService service = new OrderService();
// ❌ Como injetar as dependências?
// Os campos são privados!
// Precisa usar reflexão... complicado!
}
Problema 3: Dependências Escondidas
@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 dependências!
}
Problema: Não é fácil visualizar todas as dependências!
Com construtor:
public OrderService(Dep1 d1, Dep2 d2, Dep3 d3, Dep4 d4, Dep5 d5) {
// ❌ UAU! Muitas dependências!
// ➡️ Sinal de que a classe faz coisas demais
}
Resumo: Injeção por Campo
❌ Não é imutável
❌ Difícil de testar
❌ Esconde problemas de design
❌ Usa reflexão (mais lento)
👉 EVITE INJEÇÃO POR CAMPO!
Comparação dos 3 Tipos
| Critério | Construtor | Setter | Campo |
|---|---|---|---|
| Imutabilidade (final) | ✅ Sim | ❌ Não | ❌ Não |
| Dependências obrigatórias | ✅ Sim | ❌ Não | ⚠️ Não claro |
| Fácil de testar | ✅ Sim | ⚠️ Médio | ❌ Não |
| Legibilidade | ✅ Clara | ✅ Clara | ⚠️ Escondida |
| @Autowired necessário | ❌ Não | ✅ Sim | ✅ Sim |
| Recomendação | ✅ USE | ⚠️ Opcional | ❌ EVITE |
Anotações de Injeção
O Spring suporta 3 anotações para injeção:
1. @Autowired (Spring)
@Service
public class OrderService {
private final EmailService emailService;
@Autowired // Anotação do Spring
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Origem: Spring Framework
Uso: O mais comum
2. @Inject (Java Standard)
import jakarta.inject.Inject;
@Service
public class OrderService {
private final EmailService emailService;
@Inject // Anotação Jakarta (Java padrão)
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Origem: Jakarta EE (antigo Java EE)
Uso: Se você quer código portável (não dependente do Spring)
3. @Resource (Java Standard)
import jakarta.annotation.Resource;
@Service
public class OrderService {
@Resource(name = "emailService") // Injeção por nome
private EmailService emailService;
}
Origem: Jakarta EE
Diferença: Injeta por nome ao invés de tipo
Qual Escolher?
Recomendação: @Autowired (ou nenhuma para construtor)
Por quê?
✅ Padrão do Spring
✅ Opcional no construtor
✅ Mais opções (required, @Qualifier)
✅ Melhor documentada
Gerenciando Múltiplas Implementações
Problema
public interface EmailService {
void send(String to, String message);
}
@Service
public class GmailService implements EmailService { ... }
@Service
public class SendGridService implements EmailService { ... }
// ❌ O Spring não sabe qual escolher!
Solução 1: @Primary
@Service
@Primary // ← Esta implementação é padrão
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Enviando via Gmail para " + to);
}
}
@Service
public class SendGridService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Enviando via SendGrid para " + to);
}
}
O Spring usará GmailService por padrão!
Solução 2: @Qualifier
@Service
public class OrderService {
private final EmailService emailService;
public OrderService(@Qualifier("sendGridService") EmailService emailService) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Nome específico do bean
this.emailService = emailService;
}
}
O Spring usará especificamente SendGridService!
Solução 3: Nomeação Explícita
@Service("gmailEmailService") // ← Nome explícito
public class GmailService implements EmailService { ... }
@Service("sendGridEmailService") // ← Nome explícito
public class SendGridService implements EmailService { ... }
// Uso
public OrderService(@Qualifier("sendGridEmailService") EmailService service) {
this.emailService = service;
}
Dependências Opcionais
Com @Autowired(required = false)
@Service
public class NotificationService {
private final EmailService emailService;
private SmsService smsService; // Opcional
// Obrigatória
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); // Sempre
if (smsService != null) {
smsService.send(message); // Se disponível
}
}
}
Com 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));
// ^^^^^^^^^
// Chamado somente se presente
}
}
Exemplo Completo
// Interface
public interface PaymentProcessor {
void process(Order order);
}
// Implementação 1
@Service
@Primary // ← Padrão
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void process(Order order) {
System.out.println("Pagamento via Stripe: " + order.getTotal());
}
}
// Implementação 2
@Service("paypalProcessor")
public class PayPalPaymentProcessor implements PaymentProcessor {
@Override
public void process(Order order) {
System.out.println("Pagamento via PayPal: " + order.getTotal());
}
}
// Uso - Opção 1: Usar @Primary
@Service
public class OrderService {
private final PaymentProcessor paymentProcessor;
// O Spring injeta StripePaymentProcessor (@Primary)
public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}
// Uso - Opção 2: Especificar com @Qualifier
@Service
public class SpecialOrderService {
private final PaymentProcessor paymentProcessor;
// Força o uso do PayPal
public SpecialOrderService(
@Qualifier("paypalProcessor") PaymentProcessor paymentProcessor
) {
this.paymentProcessor = paymentProcessor;
}
}
Resumo do Capítulo
Os 3 Tipos de Injeção
// 1. Construtor ✅ RECOMENDADO
public MyService(Dependency dep) {
this.dep = dep;
}
// 2. Setter ⚠️ Para dependências opcionais
@Autowired(required = false)
public void setDependency(Dependency dep) {
this.dep = dep;
}
// 3. Campo ❌ A EVITAR
@Autowired
private Dependency dep;
Escolha de Implementação
// Opção 1: @Primary
@Primary
@Service
public class DefaultImpl implements MyInterface { }
// Opção 2: @Qualifier
public MyService(@Qualifier("specificImpl") MyInterface service) { }
Pontos-Chave
✅ Sempre use injeção por construtor
✅ Declare as dependências como final
✅ @Autowired opcional no construtor
✅ @Primary para implementação padrão
✅ @Qualifier para escolha específica
⚠️ Setter apenas para dependências opcionais
❌ Evite injeção por campo
Autoavaliação
- Quais são os 3 tipos de injeção?
- Por que a injeção por construtor é preferida?
- Qual a diferença entre @Primary e @Qualifier?
- Como tornar uma dependência opcional?
Próximo Passo
Agora você sabe COMO injetar dependências. Vamos avançar para a criação e o gerenciamento de Beans!