Spring

Como funciona a Injeção de Dependências no Spring

Como utilizar a injeção de dependências?

Uma das principais preocupações dos desenvolvedores com relação ao código de um projeto, é a questão da manutenção.

Queremos deixar nosso projeto preparado para receber manutenção, modificações, inclusões de novas funcionalidades e que tudo seja feito com o mínimo de retrabalho possível.

Por isso sentimos a necessidade de conhecer e trabalhar sempre com padrões de projeto, técnicas, frameworks que nos ajudem a deixar o código com o menor nível de acoplamento possível.

Neste artigo você vai aprender sobre um dos padrões de projetos mais usados atualmente: injeção de dependências.

(Este artigo foi escrito por um aluno da AlgaWorks em um desafio do curso Especialista Spring REST)

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

Injeção de Dependência é um padrão de projeto que ajuda muito a deixar o código desacoplado, melhora a legibilidade e interpretação do código, melhora a distribuição de responsabilidades entre as classes e facilita a manutenção do código.

É uma forma de aplicar inversão de controle em uma classe que utiliza funcionalidades de outras classes, tirando a responsabilidade dela de instanciar ou buscar objetos dessas outras classes das quais ela depende.

Então o objetivo é não instanciar objetos que realizam funções que podem futuramente ser alteradas dentro de uma classe e sim deixar a responsabilidade dessa instanciação para quem chama a classe.

Um exemplo com Java

Imagine que temos uma classe de serviço chamada PrecificacaoService e essa classe tem um método calcularPreco que recebe um Produto.

Esse método faz um cálculo que é igual para todos os produtos baseado nas características e no custo de produção e retorna o preço final do produto para venda, com adição de impostos de acordo com um cálculo específico da classe CalculadoraImpostoSimplesNacional.

Veja uma forma bem simplória da classe:

public class PrecificacaoService {

    private CalculadoraImpostoSimplesNacional calculadoraImposto = new CalculadoraImpostoSimplesNacional();

    public BigDecimal calcularPreco(Produto produto) {
        // faz outros cálculos de preço
        return this.calculadoraImposto.calcular(produto);
    }

}

Neste exemplo eu coloquei uma dependência da classe CalculadoraImpostoSimplesNacional diretamente dentro da classe PrecificacaoService, colocando nela a responsabilidade de instanciar e utilizar essa classe de dependência.

Isso é um problema e já vimos que a ideia é tirar a responsabilidade das classes de instanciar seus objetos de dependência.

Para resolver essa questão, poderíamos passar uma instância da classe CalculadoraImpostoSimplesNacional diretamente no construtor de PrecificacaoService.

Mas, ainda assim, estaríamos presos ao tipo de cálculo de impostos para empresas do Simples Nacional, podendo usar somente o CalculadoraImpostoSimplesNacional.

E se no futuro a gente precisasse calcular impostos para empresas com um outro enquadramento, como Lucro Presumido, ficaria bem difícil fazer essa substituição.

Por isso, o ideal nesse caso seria criar uma interface CalculadoraImposto, que declara o método abstrato calcular para ser implementado.

public interface CalculadoraImposto {

    BigDecimal calcular(Produto produto);

}

E aí é só substituir a dependência na classe PrecificacaoService para essa interface:

public class PrecificacaoService {

    private CalculadoraImposto calculadoraImposto;

    public PrecificacaoService(CalculadoraImposto calculadoraImposto) {
        this.calculadoraImposto = calculadoraImposto;
    }

    public BigDecimal calcularPreco(Produto produto) {
        // faz outros cálculos de preço
        return this.calculadoraImposto.calcular(produto);
    }

}

Veja que agora a classe PrecificacaoService não depende mais de nenhuma implementação de calculadora de imposto, mas de uma abstração de uma calculadora (interface).

Dessa forma, nós podemos implementar novas calculadoras de impostos sempre que necessário, sem que isso afete o funcionamento de PrecificacaoService.

Essa é uma forma de aplicar a injeção de dependências com Java manualmente.

Como o Spring lida com a injeção de dependências

O Spring Framework implementa injeção de dependências através de um container chamado Spring IoC Container.

Este container é o responsável por gerenciar todas as dependências do projeto de forma automática.

Os objetos gerenciados pelo container do Spring são chamados de Beans. Uma aplicação rodando pode ter vários beans ativos e gerenciados pelo Spring.

Esses beans são os mesmos objetos que nós utilizamos no projeto normalmente, a única diferença é que eles são gerenciados pelo IoC Container.

Existem várias formas de declarar um bean do Spring. A mais conhecida é anotando a classe com @Component.

Outras anotações semânticas podem ser usadas também, como por exemplo @Controller, @Service, @Repository, etc.

Os beans gerenciados pelo Spring são instanciados automaticamente pelo IoC Container e todas as dependências são resolvidas e repassadas pelo próprio framework.

A classe a seguir está declarada como um componente Spring (um bean), portanto é gerenciada pelo IoC Container:

@Component
public class PrecificacaoService {

    private CalculadoraImposto calculadoraImposto;

    public PrecificacaoService(CalculadoraImposto calculadoraImposto) {
        this.calculadoraImposto = calculadoraImposto;
    }

    public BigDecimal calcularPreco(Produto produto) {
        return this.calculadoraImposto.calcular(produto);
    }

}

A ideia principal é injetar beans em outros beans, ou seja, eu só posso injetar um objeto em outro objeto se eles forem gerenciados pelo Spring.

Então no caso do nosso exemplo, ficaria assim:


@Component
public class CalculadoraImpostoSimplesNacional implements CalculadoraImposto {

    public BigDecimal calcular(Produto produto) {
        // Faz o cálculo....
    }

}

@Component
public class PrecificacaoService {

    private CalculadoraImposto calculadoraImposto;

    public PrecificacaoService(CalculadoraImposto calculadoraImposto) {
        this.calculadoraImposto = calculadoraImposto;
    }

    public BigDecimal calcularValor(Produto produto) {
        // Faz outros cálculos de preço
        return this.calculadoraImposto.calcular(produto);
    }

}

As duas classes são beans gerenciados pelo Spring, por isso ao iniciar o projeto o framework já instancia os beans e os gerencia.

A injeção da dependência do tipo CalculadoraImposto na classe PrecificacaoService é feita automaticamente pelo framework, porque temos só uma implementação de CalculadoraImposto.

Mas se a gente tivesse mais implementações de CalculadoraImposto, teríamos um erro de ambiguidade, porque o container não conseguiria decidir qual implementação é a desejada.

Daqui a pouco nós vamos ver como é possível fazer a desambiguação de beans.

Configuração de Beans

Por padrão o Spring faz a instanciação dos beans sem fazer nenhum tipo de configuração.

Se em algum cenário isso for necessário, é possível criar uma classe Java com a anotação @Configuration e definir métodos com os nomes dos beans a serem instanciados e gerenciados pelo container.

Nesses métodos podemos fazer a configuração de forma mais personalizada, por exemplo:

@Configuration
public class CalculadoraImpostoConfig {

    @Bean
    public CalculadoraImpostoSimplesNacional calculadoraImpostoSimplesNacional() {
        BigDecimal aliquotaImposto = new BigDecimal(17.65);
        return new CalculadoraImpostoSimplesNacional(aliquotaImposto);
    }

}

Definindo essa classe, toda vez que o Spring precisar instanciar a classe CalculadoraImpostoSimplesNacional, ele irá chamar o método anotado com @Bean, que constrói o objeto de forma personalizada.

Pontos de injeção

O Spring utiliza o construtor da classe como ponto de injeção, como já vimos:

@Component
public class PrecificacaoService {

    private CalculadoraImposto calculadoraImposto;

    // O construtor aqui é o ponto de injeção
    public PrecificacaoService(CalculadoraImposto calculadoraImposto) {
        this.calculadoraImposto = calculadoraImposto;
    }

    public BigDecimal calcularPreco(Produto produto) {
        return this.calculadoraImposto.calcular(produto);
  }

}

Mas o que acontece se o construtor da classe tiver uma sobrecarga?

Neste caso, precisamos indicar para o Spring qual é o ponto de injeção que ele deve utilizar e fazemos isso com a anotação @Autowired. Assim:

@Component
public class PrecificacaoService {

    private CalculadoraImposto calculadoraImposto;

    // Esse é o ponto de injeção
    @Autowired
    public PrecificacaoService(CalculadoraImposto calculadoraImposto) {
        this.calculadoraImposto = calculadoraImposto;
    }

    public PrecificacaoService(String algumaCoisa) {
        // Uma sobrecarga do construtor...
    }

    public BigDecimal calcularPreco(Produto produto) {
        return this.calculadoraImposto.calcular(produto);
    }

}

Mas o construtor não é o único ponto de injeção. É possível usarmos a anotação @Autowired em um método setter ou na própria variável de instância.

Por exemplo, veja como ficaria em um setter:

@Component
public class PrecificacaoService {

    private CalculadoraImposto calculadoraImposto;

    public BigDecimal calcularPreco(Produto produto) {
        return this.calculadoraImposto.calcular(produto);
    }

    // Agora o ponto de injeção é aqui
    @Autowired
    public void setCalculadoraImposto(CalculadoraImposto calculadoraImposto) {
        this.calculadoraImposto = calculadoraImposto;
    }

}

Veja agora um exemplo na variável de instância:

@Component
public class PrecificacaoService {

    // Agora o ponto de injeção é aqui
    @Autowired
    private CalculadoraImposto calculadoraImposto;

    public BigDecimal calcularPreco(Produto produto) {
        return this.calculadoraImposto.calcular(produto);
    }

}

Desambiguação dos Beans

Considerando o exemplo em que temos a classe CalculadoraImpostoSimplesNacional, imagine que que precisamos criar uma nova implementação chamada CalculadoraImpostoLucroPresumido, tendo um cenário mais ou menos assim:

@Component
public class CalculadoraImpostoSimplesNacional implements CalculadoraImposto {

    public BigDecimal calcular(Produto produto) {
        // Faz o cálculo...
    }

}

@Component
public class CalculadoraImpostoLucroPresumido implements CalculadoraImposto {

    public BigDecimal calcular(Produto produto) {
        // Faz o cálculo...
    }

}

@Component
public class PrecificacaoService {

    @Autowired
    private CalculadoraImposto calculadoraImposto;

    public BigDecimal calcularPreco(Produto produto) {
        // Faz outros cálculos de preço
        return this.calculadoraImposto.calcular(produto);
    }

}

Nesse caso o Spring não saberá qual das implementações de CalculadoraImposto deverá injetar no objeto da classe PrecicificacaoService e causará uma exceção de ambiguidade.

Uma forma de resolver isso é usando a anotação @Primary em uma das classes de implementação.

Dessa forma, nós indicamos qual é a classe principal (primária), que deverá ser usada na injeção de dependência quando houver um caso de ambiguidade.

@Primary
@Component
public class CalculadoraImpostoLucroPresumido implements CalculadoraImposto {

    public BigDecimal calcular(Produto produto) {
        // Faz o calculo...
    }

}

Neste caso, a CalculadoraImpostoLucroPresumido é quem será injetada em PrecificacaoService.

Outra forma de fazer essa indicação é utilizando a anotação @Qualifier.

Com essa anotação, nós podemos definir um nome que identifica o Bean e indicar qual queremos usar no ponto de injeção.

@Qualifier("simplesNacional")
@Component
public class CalculadoraImpostoSimplesNacional implements CalculadoraImposto {

    public BigDecimal calcular(Produto produto) {
        // Faz o cálculo...
    }

}

@Qualifier("lucroPresumido")
@Component
public class CalculadoraImpostoLucroPresumido implements CalculadoraImposto {

    public BigDecimal calcular(Produto produto) {
        // Faz o cálculo...
    }

}

@Component
public class PrecificacaoService {

    // Aqui nós qualificamos qual bean queremos injetar
    @Qualifier("lucroPresumido")
    @Autowired
    private CalculadoraImposto calculadoraImposto;

    public BigDecimal calcularPreco(Produto produto) {
        // Faz outros cálculos de preço
        return this.calculadoraImposto.calcular(produto);
    }

}

Conclusão

Se você já trabalha ou pretende trabalhar com Spring, certamente precisará usar injeção de dependências.

Espero que este artigo tenha te ajudado a entender e utilizar corretamente este padrão de projeto.

Agora, se você quer mergulhar fundo não só em Spring Framework, mas também em Spring Boot, Spring MVC, Spring Security, Spring Data JPA, Spring HATEOAS e desenvolvimento de REST APIs, conheça o curso da AlgaWorks que eu sou aluno e me ajudou muito: Especialista Spring REST.

Um abraço e até mais!

Aluno do Especialista Spring REST, formado em Análise e Desenvolvimento de Sistemas, eterno estudante de tecnologias, futuro Engenheiro de Software.

Olá,

o que você achou deste conteúdo? Conte nos comentários.

Junte-se a mais de 100.000 pessoas

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

Você se Inscreveu com Sucesso!