Pular para o conteúdo principal

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​

  1. Os 3 tipos de injeção de dependências
  2. Por que a injeção por construtor é preferida
  3. Como usar @Autowired, @Inject, @Resource
  4. 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:

  1. Injeção por Construtor ✅ (Recomendada)
  2. Injeção por Setter
  3. 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érioConstrutorSetterCampo
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​

  1. Quais são os 3 tipos de injeção?
  2. Por que a injeção por construtor é preferida?
  3. Qual a diferença entre @Primary e @Qualifier?
  4. 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!

➡️ Capítulo 3: Beans e Contexto Spring

⬅️ Capítulo anterior: IoC

⬅️ Voltar ao Módulo 5