Olá, pessoal! Hoje, no meu primeiro post aqui, quero falar com vocês sobre tudo que sei (até o momento) sobre injeção de dependência, vulgo DI. Definindo esse conceito em poucas palavras, a injeção de dependência é uma técnica que permite que uma classe receba suas dependências de fora, em vez de criá-las internamente. Isso torna nosso código mais flexível, fácil de testar e de manter. Essa abordagem é amplamente usada no framework Spring. Vamos agora entender um pouco como ela é útil e como aplicá-la. Todos os exemplos estarão em Java.
Problema
Antes de aprender de fato sobre DI, temos que entender qual problema ela esta resolvendo. Imagine que você tem uma classe carro
. que precisa de um motor
para funcionar. Sem DI, provavelmente, seria feito algo assim:
public class Carro {
private Motor motor = new Motor();
}
Veja o problema: O Carro
esta criando o motor
diretamente. Isso esta gerando um forte acoplamento, ou seja, o carro depende de uma implementação especifica do motor. Então, se por acaso eu quiser trocar o Motor
por um MotorEletrico
ou estar o carro com algum outro tipo de motor, fica difícil. Além disso, em futuros testes unitários, não é possível isolar unicamente o carro para testar, pois ele sempre precisa do Motor
para existir. O DI resolve isso permitindo que o motor
seja fornecido externamente, no lugar de criar dentro da classe. Outra analogia interessante, sem DI é como se você cozinhasse sua própria comida e com DI é quando você pede Ifood. A sua implementação (comida) vem de fora.
Resolvendo o problema
Agora que sabemos da existência do problema, o próximo passo é resolver esse problema, aplicando a injeção de dependência. Vou listar três formas de aplica-la.
-
Injeção pelo construtor:
Aqui o motor é passado pelo construtor. É uma abordagem simples e garante que o
carro
sempre tenha ummotor
.Essa é uma das formas mais indicadas pelo fato de permitir imutabilidade (se você marcar os campos dos atributos como
final
); clareza, pois todas as dependências são explicitas pelo construtor; facilita os testes, pois permite que você passe mocks pelo construtor; segurança, pois evita problemas de dependências nulas; e por fim, o próprio pessoal do Spring sugere a injeção de dependência via construtor (você pode ver isso aqui e aqui também)
public class Carro { private Motor motor; public Carro(Motor motor) { this.motor = motor; } }
Injeção pelo setter
Aqui a injeção é feita via método setter
. Você consegue mudar o motor depois que o carro foi criado, porém não tem garantia que a dependência esteja sempre presente. Essa abordagem pode ser útil quando temos dependências opcionais.
public class Carro {
private Motor motor;
public void setMotor(Motor motor) {
this.motor = motor;
}
}
- Usando @Autowired
Essa é a abordagem menos indicada atualmente, principalmente pela sua falta de clareza. Como a anotação @Autowired
é usada direto nos campos, você tem que ler praticamente a classe inteira para descobrir suas dependências. O segundo ponto é o forte acoplamento ao contêiner do Spring para resolver suas dependências automaticamente, então isso vai dificultar testes unitários, fazendo com que seja necessário usar, por exemplo, ferramentas de reflexão (Mockito com o seu @InjectMocks
. A solução para os problemas citados é a injeção via construtor. Porém, mesmo assim vou deixar o exemplo que vem sendo utilizado até aqui usando essa anotação:
public class Carro {
private Motor motor;
@Autowired
public Carro(Motor motor) {
this.motor = motor;
}
}
Boas Práticas
Algumas boas práticas sugeridas ao usar DI são:
-
Usar Interfaces para Dependências: Defina dependências como interfaces, não como classes concretas. Por exemplo, use a interface
Motor
com implementações comoMotorEletrico
ouMotorGasolina
. Isso permite que diferentes implementações sejam injetadas sem alterar o código da classe consumidora, promovendo maior desacoplamento no sistema:
public interface Motor { void ligar(); } @Component public class MotorEletrico implements Motor { public void ligar() { System.out.println("Motor elétrico ligado!"); } } @Component public class Carro { private final Motor motor; public Carro(Motor motor) { this.motor = motor; } public void andar() { motor.ligar(); System.out.println("Carro andando!"); } }
Única Responsabilidade (Princípio da Responsabilidade Única):
O Princípio da Responsabilidade Única (Single Responsibility Principle, ou SRP), parte do acrônimo SOLID. Irei falar mais sobre isso futuramente, mas preciso contextualizar para essa explicação. Então o SRP afirma que uma classe deve ter apenas uma razão para mudar. Em outras palavras, cada classe deve ter uma única responsabilidade bem definida, evitando que ela faça mais do que o necessário. No contexto da injeção de dependência, isso significa que a classe consumidora (como o Carro
) não deve se preocupar com os detalhes de implementação de suas dependências (como o Motor
). O Carro
precisa apenas saber como usar o Motor
(ex.: ligá-lo), sem entender como ele funciona internamente (ex.: combustão ou eletricidade).
Por exemplo, imagine que o Carro
tentasse controlar diretamente o funcionamento interno do Motor
, como ajustar a voltagem de um motor elétrico ou misturar combustível em um motor a gasolina. Isso violaria o SRP, pois o Carro
estaria assumindo responsabilidades que pertencem ao Motor
. Com a injeção de dependência, podemos delegar essas responsabilidades à implementação correta do Motor
, mantendo o Carro
focado em sua tarefa principal: coordenar o movimento do veículo. Veja como isso funciona no código:
public interface Motor {
void ligar();
}
@Component
public class MotorEletrico implements Motor {
public void ligar() {
// Lógica complexa para ativar motor elétrico
System.out.println("Motor elétrico ligado com bateria!");
}
}
@Component
public class Carro {
private final Motor motor;
public Carro(Motor motor) {
this.motor = motor;
}
public void andar() {
motor.ligar(); // Carro apenas "usa" o motor
System.out.println("Carro andando!");
}
}
Nesse exemplo, o Carro não sabe se o Motor é elétrico, a gasolina ou híbrido. Ele simplesmente chama o método ligar() definido na interface Motor. Isso mantém o Carro com uma única responsabilidade (gerenciar o movimento) e delega o comportamento do motor às suas implementações. No Spring, o contêiner garante que a implementação correta (como MotorEletrico
) seja injetada, reforçando a separação de responsabilidades.
Recapitulando
Chegamos ao final da nossa introdução à injeção de dependência (DI) no Spring, então, vou deixar um resumo sobre o que aprendemos até aqui sobre DI. O DI uma técnica poderosa que permite que uma classe receba suas dependências de fora, em vez de criá-las internamente, reduzindo o acoplamento e facilitando testes e manutenção. Vimos que, sem DI, classes como o Carro ficam presas a implementações específicas (ex.: um Motor concreto), dificultando mudanças e testes. Com DI, podemos injetar dependências de forma flexível, usando três abordagens principais:
-
Injeção pelo Construtor: A forma mais recomendada, pois garante imutabilidade (com campos final), clareza (dependências explícitas), segurança (evita nulos) e facilidade para testes. O Spring a incentiva desde a versão 4.3, até permitindo omitir a anotação
@Autowired
em construtores únicos. - Injeção por Setter: Útil para dependências opcionais ou reconfiguração dinâmica, mas menos comum, pois não garante inicialização completa e perde em imutabilidade.
- Injeção por Campo com @Autowired: Menos indicada hoje, pois esconde dependências, complica testes e aumenta o acoplamento ao Spring. Prefira construtores para dependências obrigatórias.
Também exploramos boas práticas para tirar o máximo proveito da DI:
- Use interfaces (ex.: Motor com implementações como
MotorEletrico
) para promover desacoplamento e flexibilidade. - Siga o Princípio da Responsabilidade Única, garantindo que cada classe (como o Carro) tenha uma única função e delegue responsabilidades específicas às suas dependências.
A DI, combinada com essas práticas, torna seu código mais modular, testável e fácil de manter. No Spring, o contêiner gerencia automaticamente as dependências, permitindo que você foque na lógica do negócio. Agora que você conhece os fundamentos, experimente aplicar DI em seus projetos e consulte a documentação do Spring para aprofundar seus conhecimentos, meu intuito aqui era fazer apenas uma introdução para quem nunca estudou sobre o tema. O segundo intuito é documentar minha jornada de aprendizado. Sempre dizem que quem ensina, aprende duas vezes, então vou colocar isso em prática. Assim, posso ajudar outras pessoas, revisar e consultar no futuro.