TP5.1 - Injeção de Dependências Avançada
Objetivos
Ao final deste exercício, você será capaz de:
- ✅ Utilizar os 3 tipos de injeção (construtor, setter, campo)
- ✅ Escolher entre várias implementações com @Qualifier
- ✅ Gerenciar dependências opcionais
- ✅ Compreender e resolver dependências circulares
Duração estimada: 1h30
Pré-requisitos
- ✅ Capítulos 1 e 2 do Módulo 5 concluídos
- ✅ Maven 3.9+ instalado
- ✅ Java 17+ instalado
- ✅ VS Code com extensões Java
Preparação: Criar o Projeto
Etapa 1: Gerar o Projeto com Spring Initializr
Via site web:
-
Acesse https://start.spring.io/
-
Configure:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.0 (ou última versão)
- Group:
com.example - Artifact:
injection-demo - Name:
injection-demo - Package name:
com.example.injection - Packaging: Jar
- Java: 17
-
Dependências a adicionar:
- Spring Web
-
Clique em GENERATE
-
Extraia o arquivo e abra no VS Code
Etapa 2: Estrutura do Projeto
Crie a seguinte estrutura:
src/main/java/com/example/injection/
├── InjectionDemoApplication.java
├── service/
│ ├── EmailService.java
│ ├── GmailService.java
│ ├── SendGridService.java
│ └── NotificationService.java
├── controller/
│ └── TestController.java
└── config/
└── AppConfig.java
Exercício 1: Injeção por Construtor ✅
Objetivo
Criar um serviço com injeção por construtor (boa prática).
Etapa 1.1: Criar a Interface EmailService
Arquivo: service/EmailService.java
package com.example.injection.service;
public interface EmailService {
void send(String to, String message);
String getProviderName();
}
O que estamos fazendo: Criar uma interface para permitir múltiplas implementações.
Etapa 1.2: Criar a Implementação GmailService
Arquivo: service/GmailService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("[Gmail] Envio de email para " + to + ": " + message);
}
@Override
public String getProviderName() {
return "Gmail";
}
}
Pontos-chave:
@Service→ Spring gerencia este Bean- Implementa
EmailService
Etapa 1.3: Criar o NotificationService com Injeção por Construtor
Arquivo: service/NotificationService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
// Dependência declarada como 'final' (imutável)
private final EmailService emailService;
// Injeção por construtor (BOA PRÁTICA ✅)
public NotificationService(EmailService emailService) {
this.emailService = emailService;
System.out.println("NotificationService criado com: " +
emailService.getProviderName());
}
public void notifyUser(String email, String message) {
System.out.println("=== NotificationService ===");
emailService.send(email, message);
}
}
Pontos-chave:
private final→ Imutável- Injeção via construtor
- Não precisa de
@Autowired(opcional em construtor único)
Etapa 1.4: Criar o Controller de Teste
Arquivo: controller/TestController.java
package com.example.injection.controller;
import com.example.injection.service.NotificationService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/test")
public class TestController {
private final NotificationService notificationService;
// Injeção por construtor aqui também
public TestController(NotificationService notificationService) {
this.notificationService = notificationService;
}
@GetMapping("/constructor")
public String testConstructorInjection() {
notificationService.notifyUser("user@example.com",
"Teste de injeção por construtor");
return "✅ Injeção por construtor testada - Veja o console";
}
}
Etapa 1.5: Testar
Inicie a aplicação:
mvn clean install -DskipTests spring-boot:run
Teste no navegador:
http://localhost:8080/api/test/constructor
O console deve exibir:
NotificationService créé avec: Gmail
=== NotificationService ===
[Gmail] Envoi email à user@example.com: Test injection par constructeur
✅ Resultado esperado: "✅ Injeção por construtor testada - Veja o console"
Exercício 2: Gerenciar Múltiplas Implementações com @Qualifier
Objetivo
Ter 2 implementações de EmailService e escolher qual utilizar.
Etapa 2.1: Criar SendGridService
Arquivo: service/SendGridService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service("sendGridService") // ← Nome explícito
public class SendGridService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("[SendGrid] Envio de email para " + to + ": " + message);
}
@Override
public String getProviderName() {
return "SendGrid";
}
}
Etapa 2.2: Problema - Duas Implementações!
Reinicie a aplicação:
mvn clean install -DskipTests spring-boot:run
Você deve ver um ERRO:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.injection.service.NotificationService
required a single bean, but 2 were found:
- gmailService: defined in file [...]
- sendGridService: defined in file [...]
Action:
Consider marking one of the beans as @Primary, or use @Qualifier
Isso é normal! O Spring não sabe qual escolher.
Etapa 2.3: Solução 1 - @Primary
Altere GmailService.java:
package com.example.injection.service;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Service
@Primary // ← Implementação padrão
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("[Gmail] Envio de email para " + to + ": " + message);
}
@Override
public String getProviderName() {
return "Gmail";
}
}
Reinicie: A aplicação agora inicia! Gmail é usado por padrão.
Teste:
http://localhost:8080/api/test/constructor
Console:
[Gmail] Envio de email para user@example.com: ...
Etapa 2.4: Solução 2 - @Qualifier para Escolher
Crie um novo serviço que utiliza SendGrid:
Arquivo: service/SendGridNotificationService.java
package com.example.injection.service;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class SendGridNotificationService {
private final EmailService emailService;
// Usar @Qualifier para escolher SendGrid
public SendGridNotificationService(
@Qualifier("sendGridService") EmailService emailService
) {
this.emailService = emailService;
System.out.println("SendGridNotificationService criado com: " +
emailService.getProviderName());
}
public void notifyUser(String email, String message) {
System.out.println("=== SendGridNotificationService ===");
emailService.send(email, message);
}
}
Etapa 2.5: Testar @Qualifier
Adicione um método em TestController.java:
@RestController
@RequestMapping("/api/test")
public class TestController {
private final NotificationService notificationService;
private final SendGridNotificationService sendGridNotificationService;
public TestController(
NotificationService notificationService,
SendGridNotificationService sendGridNotificationService
) {
this.notificationService = notificationService;
this.sendGridNotificationService = sendGridNotificationService;
}
@GetMapping("/constructor")
public String testConstructorInjection() {
notificationService.notifyUser("user@example.com",
"Teste com Gmail");
return "✅ Gmail utilizado - Veja o console";
}
@GetMapping("/qualifier")
public String testQualifier() {
sendGridNotificationService.notifyUser("user@example.com",
"Teste com SendGrid");
return "✅ SendGrid utilizado via @Qualifier - Veja o console";
}
}
Teste:
http://localhost:8080/api/test/qualifier
Console:
=== SendGridNotificationService ===
[SendGrid] Envio de email para user@example.com: Teste com SendGrid
✅ Você tem 2 serviços usando implementações diferentes!
Exercício 3: Dependências Opcionais
Objetivo
Criar um serviço que pode funcionar com ou sem uma dependência.
Etapa 3.1: Criar um SmsService (Opcional)
Arquivo: service/SmsService.java
package com.example.injection.service;
public interface SmsService {
void send(String phone, String message);
}
Não crie uma implementação ainda!
Etapa 3.2: Criar CompleteNotificationService com Dependência Opcional
Arquivo: service/CompleteNotificationService.java
package com.example.injection.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class CompleteNotificationService {
private final EmailService emailService;
private final Optional<SmsService> smsService; // ← Opcional!
public CompleteNotificationService(
EmailService emailService,
Optional<SmsService> smsService // ← Wrapper Optional
) {
this.emailService = emailService;
this.smsService = smsService;
System.out.println("CompleteNotificationService criado");
System.out.println("- Email: " + emailService.getProviderName());
System.out.println("- SMS: " + (smsService.isPresent() ? "Disponível" : "Não disponível"));
}
public void notify(String email, String phone, String message) {
// Email sempre enviado
emailService.send(email, message);
// SMS somente se disponível
smsService.ifPresent(sms -> sms.send(phone, message));
}
}
Pontos-chave:
Optional<SmsService>→ Pode estar ausenteifPresent()→ Executado apenas se presente
Etapa 3.3: Testar Sem SmsService
Adicione em TestController.java:
private final CompleteNotificationService completeService;
public TestController(
NotificationService notificationService,
SendGridNotificationService sendGridNotificationService,
CompleteNotificationService completeService
) {
this.notificationService = notificationService;
this.sendGridNotificationService = sendGridNotificationService;
this.completeService = completeService;
}
@GetMapping("/optional")
public String testOptional() {
completeService.notify("user@example.com", "123-456-7890",
"Teste dependência opcional");
return "✅ Email enviado, SMS não disponível - Veja o console";
}
Teste:
http://localhost:8080/api/test/optional
Console:
CompleteNotificationService criado
- Email: Gmail
- SMS: Não disponível
[Gmail] Envio de email para user@example.com: Teste dependência opcional
✅ A aplicação funciona mesmo sem SmsService!
Etapa 3.4: Adicionar SmsService e Testar Novamente
Arquivo: service/SimpleSmsService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service
public class SimpleSmsService implements SmsService {
@Override
public void send(String phone, String message) {
System.out.println("[SMS] Envio para " + phone + ": " + message);
}
}
Reinicie e teste:
http://localhost:8080/api/test/optional
Console:
CompleteNotificationService criado
- Email: Gmail
- SMS: Disponível
[Gmail] Envio de email para user@example.com: Teste dependência opcional
[SMS] Envio para 123-456-7890: Teste dependência opcional
✅ Agora ambos são utilizados!
Exercício 4: Injetar Todas as Implementações
Objetivo
Recuperar TODAS as implementações de uma interface.
Etapa 4.1: Criar MultiChannelNotificationService
Arquivo: service/MultiChannelNotificationService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MultiChannelNotificationService {
private final List<EmailService> allEmailServices;
// Spring injeta uma Lista com TODAS as implementações
public MultiChannelNotificationService(List<EmailService> allEmailServices) {
this.allEmailServices = allEmailServices;
System.out.println("MultiChannelNotificationService criado com " +
allEmailServices.size() + " serviços de email:");
allEmailServices.forEach(service ->
System.out.println(" - " + service.getProviderName())
);
}
public void broadcastEmail(String to, String message) {
System.out.println("=== Envio via TODOS os providers ===");
allEmailServices.forEach(service -> service.send(to, message));
}
}
Etapa 4.2: Testar
Adicione em TestController.java:
private final MultiChannelNotificationService multiChannelService;
public TestController(
NotificationService notificationService,
SendGridNotificationService sendGridNotificationService,
CompleteNotificationService completeService,
MultiChannelNotificationService multiChannelService
) {
this.notificationService = notificationService;
this.sendGridNotificationService = sendGridNotificationService;
this.completeService = completeService;
this.multiChannelService = multiChannelService;
}
@GetMapping("/all")
public String testAllImplem() {
multiChannelService.broadcastEmail("user@example.com",
"Mensagem broadcast");
return "✅ Envio via todos os providers - Veja o console";
}
Teste:
http://localhost:8080/api/test/all
Console:
=== Envio via TODOS os providers ===
[Gmail] Envio de email para user@example.com: Mensagem broadcast
[SendGrid] Envio de email para user@example.com: Mensagem broadcast
✅ As 2 implementações são utilizadas!
Exercício 5: Injeção por Setter (Dependências Opcionais)
Objetivo
Utilizar a injeção por setter para dependências realmente opcionais.
Etapa 5.1: Criar LoggerService com Setter
Arquivo: service/LoggerService.java
package com.example.injection.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoggerService {
private SmsService smsService; // ← Não é 'final', pode ser null
// Injeção por setter - OPCIONAL
@Autowired(required = false) // ← required = false
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
System.out.println("SMS Service injetado no LoggerService");
}
public void logNotification(String message) {
System.out.println("[LOG] " + message);
if (smsService != null) {
System.out.println("[LOG] SMS Service disponível");
} else {
System.out.println("[LOG] SMS Service não disponível");
}
}
}
Pontos-chave:
@Autowired(required = false)→ Não obrigatório- Verificação
if (smsService != null)
Etapa 5.2: Testar
Adicione em TestController.java:
private final LoggerService loggerService;
@GetMapping("/setter")
public String testSetter() {
loggerService.logNotification("Teste de injeção por setter");
return "✅ Injeção por setter testada - Veja o console";
}
Teste:
http://localhost:8080/api/test/setter
Console (com SmsService):
SMS Service injetado no LoggerService
[LOG] Teste de injeção por setter
[LOG] SMS Service disponível
Recapitulação: Comparação dos Métodos
Tabela Comparativa
| Tipo | Sintaxe | Obrigatório? | Imutável? | Uso |
|---|---|---|---|---|
| Construtor | public Service(Dep d) | ✅ Sim | ✅ Sim (final) | Recomendado para dependências obrigatórias |
| Setter | @Autowired(required=false) | ❌ Não | ❌ Não | Dependências opcionais |
| Campo | @Autowired private Dep d | ⚠️ Sim | ❌ Não | ❌ Evitar |
Optional<T> | Optional<Dep> d | ❌ Não | ✅ Sim | Dependências opcionais modernas |
List<T> | List<Dep> all | ✅ Sim | ✅ Sim | Todas as implementações |
Código Final Completo
Seu projeto agora deve ter:
✅ GmailService (@Primary)
✅ SendGridService
✅ NotificationService (construtor + Gmail)
✅ SendGridNotificationService (construtor + @Qualifier)
✅ CompleteNotificationService (Optional<SmsService>)
✅ SimpleSmsService (opcional)
✅ MultiChannelNotificationService (List<EmailService>)
✅ LoggerService (setter + required=false)
Autoavaliação
Responda às seguintes perguntas:
- Por que a injeção por construtor é preferida?
- Qual a diferença entre @Primary e @Qualifier?
- Como gerenciar uma dependência realmente opcional?
- Como injetar todas as implementações de uma interface?
- Quando usar
Optional<T>vs@Autowired(required=false)?
Se você consegue responder todas essas perguntas, você dominou a injeção de dependências! ✅
Desafio Bônus 🎯
Crie um sistema de notificação multicanal:
- Interface
NotificationChannel - Implementações:
EmailChannel,SmsChannel,PushChannel - Serviço
NotificationOrchestratorque:- Injeta todos os canais
- Permite ativar/desativar canais
- Envia em todos os canais ativos
Tempo estimado: 30 minutos
Próximo Passo
Agora você domina a injeção de dependências! Avance para o TP2 para aprender sobre Beans e Escopos.