DEV Community

Armando Tadeu
Armando Tadeu

Posted on

Injeção de Dependências com Spring

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.

A 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, melhora a distribuição de responsabilidades entre as classes e facilita a manutenção do código.

Definição objetiva

Uma descrição bem objetiva sobre o que significa injeção de dependência é: 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 serem 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 ValorVendaService e essa classe tem um método calculaValor que recebe um Produto faz um cálculo que é igual para todos os produtos baseado nas características e custo de produção e retorna o valor final do preço do produto para venda com adição de impostos de acordo com cálculo especifico chamado só para exemplificar de CalculoImpostoModelo1. Veja uma forma bem simplória da classe:

public class ValorVendaService {
  private CalculoImpostoModelo1 calculoImpostoModelo1 = new CalculoImpostoModelo1();

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImpostoModelo1.calcula(produto)
  }
}
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo eu coloquei uma dependência da classe CalculoImpostoModelo1 diretamente dentro da classe de ValorVendaService colocando nela a responsabilidade de instanciar e utilizar essa classe de dependência. Isso é um problema e ja 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 CalculoImpostoModelo1 diretamente no construtor de ValorVendaService. Mas ainda sim estaríamos presos ao cálculo de impostos somente podendo usar o Modelo1 e se mais tarde precisarmos trocar para um cálculo Modelo2, Modelo3, etc. ?

Por isso, o ideal nesse caso seria criar uma interface CalculoImposto que tem um método calcula para ser implementado e ai usar a interface como referência para ValorVendaService:

public interface CalculoImposto {
  void calcula(Produto produto);
}

public class ValorVendaService {
  private CalculoImposto calculoImposto;

  public valorVendaService(CalculoImposto calculoImposto) {
    this.calculoImposto = calculoImposto;
  }

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Como o Spring Lida com Injeção de Dependências

O Spring lida com injeção de dependências utilizando um Spring IoC Container conhecido como Spring Context. Esse 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, pois, esses beans são os mesmos objetos que nós utilizamos no projeto normalmente, a única diferença é que a classe deles recebe uma anotação especial que determina que os objetos dessa classe devem ser gerenciados pelo Ioc container.

Essa anotação é a @Component, porém, existem outras anotações que fazem outras coisas mas também são um @Component em sua essência como por exemplo, @Controller, @Service, etc.

Então toda classe que nós colocarmos a anotação @Component já é instanciada automaticamente pelo Spring e se torna um Bean dentro do Contexto do Ioc Container.

Essa classe a seguir já está preparada para ser instanciada e se tornar um Bean gerenciável pelo container do Spring e poderá utilizar injeção de dependências:

@Component
public class ValorVendaService {
  private CalculoImposto calculoImposto;

  public ValorVendaService(CalculoImposto calculoImposto) {
    this.calculoImposto = calculoImposto;
  }

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }
}
Enter fullscreen mode Exit fullscreen mode

A ideia principal é injetar Beans em outros Beans, ou seja, eu só posso injetar um objeto em outro objeto se as classes deles forem gerenciáveis pelo Spring, precisam ser um @Component.

Então no caso do nosso exemplo (bem simplório, apenas ilustrativo) ficaria assim:

@Component
public class CalculoImpostoModelo1 implements CalculoImposto {
  public void calcula(Produto produto) {
    // Faz o calculo....
  }
}

@Component
public class ValorVendaService {
  private CalculoImposto calculoImposto;

  public ValorVendaService(CalculoImposto calculoImposto) {
    this.calculoImposto = calculoImposto;
  }

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }
}
Enter fullscreen mode Exit fullscreen mode

As duas classe são um @Component, então ao iniciar o projeto o Spring já instância e gerencia os Beans dessas classes e nesse caso a injeção de dependência ja está pronta, pois, só temos uma implementação de CalculoImposto e o Spring consegue entender isso. Porém, existem diversas formas de configurar as injeções de dependências, pois, quando tivermos mais implementações de CalculoImposto iremos precisar dizer qual implementação vamos utilizar.

Configuração de Beans

Por padrão o Spring faz a instanciação das dependências sem passar nenhum parâmetro para o construtor da classe ou sem fazer nenhum tipo de configuração, mas 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 o nome dos Beans a serem injetados e nesses métodos podemos fazer a configuração como de forma mais personalizada, por exemplo:

@Configuration
public class ExemploConfig {

  @Bean
  public CalculoImpostoModelo1 calculoImpostoModelo1() {
    CalculoImpostoModelo1 calculoImposto = new CalculoImpostoModelo1();
    calculoImposto.desativaTaxaExtra();

    return calculoImposto;
  }

}
Enter fullscreen mode Exit fullscreen mode

Definindo essa classe, toda vez que o Spring instanciar o CalculoImpostoModelo1, irá chamar o método desativaTaxaExtra e ali poderiamos fazer passagem de parâmetros no construtor caso fosse necessário.

Pontos de Injeção

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

@Component
public class ValorVendaService {
  private CalculoImposto calculoImposto;

  // O construtor aqui é o ponto de injeção
  public ValorVendaService(CalculoImposto calculoImposto) {
    this.calculoImposto = calculoImposto;
  }

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }
}
Enter fullscreen mode Exit fullscreen mode

Mas o que acontece se o construtor da classe tiver uma sobrecarga ? Nesse caso, precisamos indicar pro Spring qual é o ponto de injeção que ele deve utilizar e fazemos isso com a anotação @Autowired, assim:

@Component
public class ValorVendaService {
  private CalculoImposto calculoImposto;

  // Esse é o ponto de injeção
  @Autowired
  public ValorVendaService(CalculoImposto calculoImposto) {
    this.calculoImposto = calculoImposto;
  }

  public ValorVendaService(String algumaCoisa) {
    // Um construtor com sobrecarga...
  }

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }
}
Enter fullscreen mode Exit fullscreen mode

Mas o construtor não é o único ponto de injeção, é possível definirmos a anotação @Autowired em um setter ou no próprio atributo...

Exemplo no setter:

@Component
public class ValorVendaService {
  private CalculoImposto calculoImposto;

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }

  //Agora o ponto de injeção é aqui
  @Autowired
  public void setCalculoImposto(CalculoImposto calculoImposto) {
    this.calculoImposto = calculoImposto;
  }
}
Enter fullscreen mode Exit fullscreen mode

Exemplo no próprio atributo:

@Component
public class ValorVendaService {

  //Agora o ponto de injeção é aqui
  @Autowired
  private CalculoImposto calculoImposto;

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }

}
Enter fullscreen mode Exit fullscreen mode

Desambiguação dos Beans

Considerando o exemplo em que temos a classe CalculoImpostoModelo1, se precisarmos utilizar agora um CalculoImpostoModelo2 tendo um cenário mais ou menos assim:

@Component
public class CalculoImpostoModelo1 implements CalculoImposto {
  public void calcula(Produto produto) {
    // Faz o calculo....
  }
}

@Component
public class CalculoImpostoModelo2 implements CalculoImposto {
  public void calcula(Produto produto) {
    // Faz o calculo....
  }
}

@Component
public class ValorVendaService {

  //Agora o ponto de injeção é aqui
  @Autowired
  private CalculoImposto calculoImposto;

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }

}
Enter fullscreen mode Exit fullscreen mode

Nesse caso o Spring não saberá qual das implementações de CalculoImposto deverá utilizar no ValorVendaService, por isso, precisamos informar.

Uma forma de fazer isso é com a anotação @primary, colocando essa anotação em uma das classes de implementação, iremos indicar que é ela que deverá ser carregada como dependência da classe que utiliza a interface CalculoImposto.

@Component
public class CalculoImpostoModelo1 implements CalculoImposto {
  public void calcula(Produto produto) {
    // Faz o calculo....
  }
}

@Primary
@Component
public class CalculoImpostoModelo2 implements CalculoImposto {
  public void calcula(Produto produto) {
    // Faz o calculo....
  }
}

@Component
public class ValorVendaService {

  ////Ponto de injeção e usando CalculoImpostoModelo2
  @Autowired
  private CalculoImposto calculoImposto;

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }

}
Enter fullscreen mode Exit fullscreen mode

Nesse caso, a CalculoImpostoModelo2 é quem será injetada em ValorVendaService.

Outra forma de fazer essa indicação é utilizando a anotação @Qualifier, com o Qualifier temos que definir um nome que identifica o Bean e indicar qual queremos usar no ponto de injeção.

@Qualifier('modelo1')
@Component
public class CalculoImpostoModelo1 implements CalculoImposto {
  public void calcula(Produto produto) {
    // Faz o calculo....
  }
}

@Qualifier('modelo2')
@Component
public class CalculoImpostoModelo2 implements CalculoImposto {
  public void calcula(Produto produto) {
    // Faz o calculo....
  }
}

@Component
public class ValorVendaService {

  //Ponto de injeção e usando CalculoImpostoModelo2
  @Qualifier('modelo2')
  @Autowired
  private CalculoImposto calculoImposto;

  public void calculaValor(Produto produto) {
    Double valorFinal = 0.0;
    // CÁLCULOS PARA DETERMINAR O PREÇO DO PRODUTO - valorFinal

    valorFinal + this.calculoImposto.calcula(produto);
  }

}
Enter fullscreen mode Exit fullscreen mode

Esses foram alguns exemplos de como utilizar os recursos de injeção de dependência no Spring para ajudar no entendimento de como funciona (que é a ideia principal do artigo), então, para finalizar, de forma bem objetiva... O Spring utiliza um Container especial para instanciar todos os objetos gerenciáveis por ele, além de suas classes internas, nós podemos indicar nossas classes para entrarem nesse contexto gerenciável, para isso, utilizamos as anotações que definem nossa classe como um componente do Spring (@Component, @Service, @Controller...), assim toda classe gerenciável é instanciada e se torna em Bean dentro do contexto de inversão de controle do Spring, todos esses objetos podem ser injetados uns nos outros facilmente conforme as técnicas nos exemplos do artigo =).

Esse artigo foi elaborado para participar do desafio do curso Especialista Spring REST da AlgaWorks. Todo o conteúdo que consegui elaborar aqui é resultado de estudos das aulas do curso.

Top comments (0)