Pular para o conteúdo principal

TP5.1 - Injeção de Dependências Avançada

Objetivos​

Ao final deste exercício, você será capaz de:

  1. ✅ Utilizar os 3 tipos de injeção (construtor, setter, campo)
  2. ✅ Escolher entre várias implementações com @Qualifier
  3. ✅ Gerenciar dependências opcionais
  4. ✅ 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:

  1. Acesse https://start.spring.io/

  2. 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
  3. Dependências a adicionar:

    • Spring Web
  4. Clique em GENERATE

  5. 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 ausente
  • ifPresent() → 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​

TipoSintaxeObrigatório?Imutável?Uso
Construtorpublic Service(Dep d)✅ Sim✅ Sim (final)Recomendado para dependências obrigatórias
Setter@Autowired(required=false)❌ Não❌ NãoDependências opcionais
Campo@Autowired private Dep d⚠️ Sim❌ Não❌ Evitar
Optional<T>Optional<Dep> d❌ Não✅ SimDependências opcionais modernas
List<T>List<Dep> all✅ Sim✅ SimTodas 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:

  1. Por que a injeção por construtor é preferida?
  2. Qual a diferença entre @Primary e @Qualifier?
  3. Como gerenciar uma dependência realmente opcional?
  4. Como injetar todas as implementações de uma interface?
  5. 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:

  1. Interface NotificationChannel
  2. Implementações: EmailChannel, SmsChannel, PushChannel
  3. Serviço NotificationOrchestrator que:
    • 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.

➡️ TP5.2: Beans e Escopos

⬅️ Voltar ao Módulo 5