[Dicas de Programação] Inversão de Controle – Injeção de Depedência – Java – Spring Boot

Quando começamos a desenvolver aplicações Java, é comum criarmos objetos diretamente com new e passarmos as dependências manualmente. Com o tempo, isso leva a um código fortemente acoplado, difícil de testar e de manter. É nesse cenário que entram a Inversão de Controle (IoC) e a Injeção de Dependência (DI), conceitos fundamentais do ecossistema Spring.

Neste artigo, vamos explorar esses conceitos de forma prática e entender como o Spring Boot os implementa para nos ajudar a construir aplicações mais limpas e modulares.


O problema: acoplamento forte

Imagine um sistema simples de pedidos. A classe PedidoService precisa de um repositório para salvar os pedidos e de um serviço de pagamento para processar a transação. Sem nenhum framework, faríamos algo assim:

java

public class PedidoService {
private PedidoRepository repository = new PedidoRepositoryMySQL();
private PagamentoService pagamentoService = new PagamentoServiceCartao();
public void criarPedido(Pedido pedido) {
// lógica de negócio
repository.salvar(pedido);
pagamentoService.processar(pedido);
}
}

Problemas evidentes:

  • Acoplamento forte: PedidoService depende diretamente das implementações concretas PedidoRepositoryMySQL e PagamentoServiceCartao. Trocar a implementação (ex: usar outro banco ou outro gateway de pagamento) exigiria alterar o código da classe.
  • Dificuldade de teste: como as dependências são criadas internamente, não conseguimos substituí-las por mocks ou stubs em testes unitários.
  • Baixa flexibilidade: qualquer mudança em uma dependência pode impactar todas as classes que a instanciam.

A solução é inverter o controle da criação das dependências: em vez de a própria classe criá-las, ela as recebe prontas de um contêiner externo. É aí que entram IoC e DI.


O que é Inversão de Controle (IoC)?

Inversão de Controle é um princípio de design onde o controle do fluxo de execução e da criação de objetos é transferido da aplicação para um contêiner ou framework. Em vez de o programador instanciar manualmente as dependências, ele declara o que precisa e o contêiner cuida do resto.

No contexto do Spring, o contêiner de IoC é o ApplicationContext, responsável por:

  • Criar e gerenciar objetos (chamados de beans)
  • Resolver e injetar as dependências entre eles
  • Controlar o ciclo de vida dos beans

A ideia central é: “Não me chame, eu te chamo” (Don’t call us, we’ll call you). Suas classes não controlam quando ou como suas dependências são criadas; o framework assume essa responsabilidade.


O que é Injeção de Dependência (DI)?

Injeção de Dependência é a forma mais comum de implementar IoC. Consiste em passar as dependências de uma classe por meio de um mecanismo externo, em vez de criá-las internamente. Existem três formas principais de injeção:

1. Injeção por construtor (recomendada)

java

public class PedidoService {
private final PedidoRepository repository;
private final PagamentoService pagamentoService;
public PedidoService(PedidoRepository repository, PagamentoService pagamentoService) {
this.repository = repository;
this.pagamentoService = pagamentoService;
}
}

2. Injeção por setter

java

public class PedidoService {
private PedidoRepository repository;
public void setRepository(PedidoRepository repository) {
this.repository = repository;
}
}

3. Injeção por campo (com anotações)

java

public class PedidoService {
@Autowired
private PedidoRepository repository;
}

A injeção por construtor é considerada a melhor prática porque:

  • Torna as dependências explícitas e obrigatórias
  • Facilita a imutabilidade (campos final)
  • Permite testar facilmente passando mocks no construtor
  • Evita dependências cíclicas (que indicam mau design)

O contêiner Spring e as anotações

No Spring Boot, o contêiner de IoC é criado automaticamente. Para que uma classe seja gerenciada pelo Spring, precisamos anotá-la com um dos estereótipos:

  • @Component – bean genérico
  • @Service – classe de serviço (camada de negócio)
  • @Repository – classe de acesso a dados
  • @Controller / @RestController – controladores web

Essas anotações dizem ao Spring: “Crie um bean desta classe e gerencie seu ciclo de vida”.

Exemplo com Spring Boot

Vamos reescrever o exemplo anterior usando Spring Boot.

1. Definindo as interfaces e implementações

java

public interface PedidoRepository {
void salvar(Pedido pedido);
}
@Repository
public class PedidoRepositoryMySQL implements PedidoRepository {
@Override
public void salvar(Pedido pedido) {
// implementação com MySQL
}
}
public interface PagamentoService {
void processar(Pedido pedido);
}
@Service
public class PagamentoServiceCartao implements PagamentoService {
@Override
public void processar(Pedido pedido) {
// processa pagamento via cartão
}
}

2. Serviço com injeção por construtor

java

@Service
public class PedidoService {
private final PedidoRepository repository;
private final PagamentoService pagamentoService;
// O Spring injeta as dependências automaticamente
public PedidoService(PedidoRepository repository, PagamentoService pagamentoService) {
this.repository = repository;
this.pagamentoService = pagamentoService;
}
public void criarPedido(Pedido pedido) {
repository.salvar(pedido);
pagamentoService.processar(pedido);
}
}

3. Classe principal do Spring Boot

java

@SpringBootApplication
public class MinhaAplicacao {
public static void main(String[] args) {
SpringApplication.run(MinhaAplicacao.class, args);
}
}

Ao iniciar a aplicação, o Spring escaneia o pacote base, encontra as classes anotadas, cria os beans e injeta as dependências corretamente. Repare que PedidoService não precisa saber qual implementação de PedidoRepository ou PagamentoService está sendo usada – isso é resolvido pelo contêiner.


Configuração explícita com @Configuration e @Bean

Além da anotação por estereótipo, podemos declarar beans manualmente em uma classe de configuração:

java

@Configuration
public class AppConfig {
@Bean
public PedidoRepository pedidoRepository() {
return new PedidoRepositoryMySQL();
}
@Bean
public PagamentoService pagamentoService() {
return new PagamentoServiceCartao();
}
@Bean
public PedidoService pedidoService() {
return new PedidoService(pedidoRepository(), pagamentoService());
}
}

Isso é útil quando precisamos de controle fino sobre a criação de objetos ou quando integramos bibliotecas de terceiros.


Resolvendo ambiguidade: @Qualifier e @Primary

Se houver mais de uma implementação da mesma interface, o Spring precisa saber qual injetar. Por exemplo, se tivermos PagamentoServiceCartao e PagamentoServiceBoleto, podemos:

  • Usar @Primary em uma das implementações para indicar a padrão
  • Ou usar @Qualifier no ponto de injeção para especificar qual bean desejado

java

@Service
@Primary
public class PagamentoServiceCartao implements PagamentoService { ... }
@Service
public class PagamentoServiceBoleto implements PagamentoService { ... }
@Service
public class PedidoService {
private final PagamentoService pagamentoService;
public PedidoService(@Qualifier("pagamentoServiceBoleto") PagamentoService pagamentoService) {
this.pagamentoService = pagamentoService;
}
}

Benefícios da IoC/DI com Spring Boot

  • Desacoplamento: as classes dependem de abstrações (interfaces), não de implementações concretas.
  • Testabilidade: podemos injetar mocks ou stubs facilmente em testes unitários.
  • Manutenção facilitada: mudar uma implementação não afeta as classes que a utilizam.
  • Código mais limpo: menos new, menos código de configuração manual.
  • Gerenciamento de ciclo de vida: o Spring cuida da criação, inicialização e destruição dos beans.

Conclusão

A Inversão de Controle e a Injeção de Dependência são pilares do desenvolvimento moderno com Java e Spring Boot. Elas promovem um design orientado a interfaces, desacoplado e altamente testável.

Ao adotar esses conceitos, você delega ao framework a responsabilidade de montar o grafo de dependências da sua aplicação, permitindo que você foque na lógica de negócio. O Spring Boot torna esse processo ainda mais simples com suas anotações e configuração automática.

Se você ainda não utiliza IoC/DI em seus projetos, vale a pena experimentar e sentir a diferença na qualidade do código e na produtividade.


Referências:

Escrito com auxílio do Deepseek.

Deixe um comentário

About the author

Sophia Bennett is an art historian and freelance writer with a passion for exploring the intersections between nature, symbolism, and artistic expression. With a background in Renaissance and modern art, Sophia enjoys uncovering the hidden meanings behind iconic works and sharing her insights with art lovers of all levels. When she’s not visiting museums or researching the latest trends in contemporary art, you can find her hiking in the countryside, always chasing the next rainbow.