Blog especializado sobre TI

[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 fortePedidoService 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;
}

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

Search