DEV Community

Cover image for Injeção de Dependências com Spring
Daniel Gomes
Daniel Gomes

Posted on

Injeção de Dependências com Spring

Visão Geral

Na busca de melhorar a manutenção e atualização de projetos, princípios como a inversão de controle (IOC) são ferramentas poderosas. A injeção de dependências é uma das formas de realizar a IOC. Sua utilização tira da raiz as dependências do projeto, esta prática tem boa sinergia com outras metodologias que compartilham dos mesmos objetivos, veremos na prática sua utilização de forma simples e sua aplicação ao utilizar Spring.

Injeção de dependências e injeções médicas

Olhando inicialmente pode parecer estranho uma analisar uma relação além do evidente jogo de palavras entre injeções médicas e injeção de dependências, mas se visualizado da forma correta, pode ajudar na compreensão do assunto.

Injeção de dependências, trata-se de design pattern que visa inserção externa de dependência afim de diminuir o acoplamento de uma aplicação, afim de diminuir o erros, retrabalho e simplificar a implementação de novas atribuições ao projeto. Numa primeira vista pode parecer um pouco confuso, talvez um bom exemplo te ajude a entender melhor, aí que entra como a medicina e a tão aguardada vacina do Covid-19 se relaciona com o assunto.

Vacinas funcionam de diversas formas diferentes, pode ser feita com o vírus inativo, enfraquecido ou de forma sintética com mRNA. A forma que ela funciona não faz muita diferença para nós, afinal, nosso sistema imunológico já sabe exatamente o que fazer ao entrar em contato com o produto da vacina.
É exatamente a mesma ideia da injeção de dependências! A atribuição pode ser de formas diferentes e essas diferenças não vão alterar a raiz principal do nosso projeto.

Mãos na massa

Vamos aplicar isto agora, através de um projeto simples.
Temos inicialmente duas pessoas, Antônio com 67 anos e Maria com 80, ambos foram chamados para receber as doses da vacina de COVID-19 e cada um recebeu uma vacina de origem distinta.

Para representa-los usaremos a Classe Pessoa, com os atributos nome e idade, e seus respectivos getter e setter.

package vacina.di.model;

public class Pessoa { 
private String nome; 
private int idade; 

public Pessoa(String nome, int idade) { 
    this.nome = nome; 
    this.idade = idade; 
} 

public String getNome() { 
    return nome; 
} 
public int getIdade() { 
    return idade; 
} 
} 
Enter fullscreen mode Exit fullscreen mode

Para serem vacinados ambos foram ao SUS, um serviço público que aplica as vacinas, chamaremos este serviço de VacinaService, nele está a função de fazer com que as pessoas recebam a vacina. E claramente, para que eles sejam vacinados é necessário que exista uma vacina, que será um atributo desta classe. Isso é representado através da classe VacinaService:

package vacina.di.infrastructure.repository; 
import vacina.di.domain.repository.Vacina; 
import vacina.di.model.Pessoa; 
 
public class VacinaService { 
    private Vacina vacina; 
    public VacinaService(Vacina vacina) { 
        this.vacina = vacina; 
        } 
    public void ReceberVacina(Pessoa pessoa) { 
            vacina.vacinar(pessoa); 
} 
} 
Enter fullscreen mode Exit fullscreen mode

Esta vacina tem como função ser aplicada, e o uso dela como uma Interface é a importante para o baixo acoplamento.
Os diferentes tipos de vacina para COVID-19, e posteriormente de qualquer outra campanha poderão ser adicionadas e alteradas a partir da implementação da interface, este é o poder da inversão de controle. A interface fica da seguinte forma:

package vacina.di.domain.repository; 
import vacina.di.model.Pessoa; 
public interface Vacina { 
 
    void vacinar(Pessoa pessoa); 
} 
Enter fullscreen mode Exit fullscreen mode

Agora resta a adição das diferentes formas de vacina, estas classes irão implementar a classe Vacina e realizar override sobre o método vacinar, inserindo seu funcionamento distinto. Temos aqui dois exemplos na classe VacinaPfizer e VacinaAstraZeneca:

package vacina.di.infrastructure.repository.vacinas; 
import vacina.di.domain.repository.Vacina; 
import vacina.di.model.Pessoa; 

public class VacinaPfizer implements Vacina { 
    @Override 
    public void vacinar(Pessoa pessoa) { 

        System.out.printf("%s tem %d anos e recebeu a primeira dose da vacina, aguarde 15 dias para a segunda dose!\n",  
        pessoa.getNome(), 
        pessoa.getIdade()); 

        System.out.println("Seu sistema imunológico recebeu uma versão inativa do vírus"); 
} 
} 
Enter fullscreen mode Exit fullscreen mode
package vacina.di.infrastructure.repository.vacinas; 
import vacina.di.domain.repository.Vacina; 
import vacina.di.model.Pessoa; 
 
public class VacinaAstraZeneca implements Vacina { 

    @Override 
    public void vacinar(Pessoa pessoa) { 
        System.out.printf("%s tem %d anos e recebeu a primeira dose da vacina, aguarde 15 dias para a segunda dose!\n",  
        pessoa.getNome(), 
        pessoa.getIdade()); 
        System.out.println("Seu sistema imunológico recebeu uma versão enfraquecida do vírus"); 
} 

} 
Enter fullscreen mode Exit fullscreen mode

Agora com toda a lógica implementada, podemos finalmente visualizar como fica o resultado final da nossa utilização da injeção de dependências na vacinação das personagens. É evidente a simplificação da instância, apenas construindo outro objeto já é feita toda execução do processo, sem alterar uma linha interna de código sequer. A classe main fica da seguinte forma:

package vacina.di; 
import vacina.di.domain.repository.Vacina; 
import vacina.di.infrastructure.repository.vacinas.VacinaAstraZeneca; 
import vacina.di.infrastructure.repository.vacinas.VacinaPfizer; 
import vacina.di.model.Pessoa; 
 
public class Main { 
 
    public static void main(String[] args) { 

        SpringApplication.run(VacinaApiApplication.class, args);
        Vacina vacina1 = new VacinaAstraZeneca();
        VacinaService vacinaService1 = new VacinaService(vacina1);
        Pessoa p1 = new Pessoa ("Antônio", 67);
        vacinaService1.ReceberVacina(p1);

        System.out.println("-----");

        Vacina vacina2 = new VacinaPfizer();
        VacinaService vacinaService2 = new VacinaService(vacina2);
        Pessoa p2 = new Pessoa ("Maria", 80);
        vacinaService2.ReceberVacina(p2);
} 

Enter fullscreen mode Exit fullscreen mode

E por fim a saída será.

Antônio tem 67 anos e recebeu a primeira dose da vacina, aguarde 15 dias para a segunda dose! 
Seu sistema imunológico recebeu uma versão enfraquecida do vírus 
----- 
Maria tem 80 anos e recebeu a primeira dose da vacina, aguarde 15 dias para a segunda dose! 
Seu sistema imunológico recebeu uma versão inativa do vírus 
Enter fullscreen mode Exit fullscreen mode

Spring Boot IOC Container e sua competência

Para darmos continuidade ao projeto, primordialmente precisamos conhecer o Spring IOC Container. Ele é responsável pelos objetos gerenciados Spring, chamados de Beans. É um dos pontos fortes do Spring, visto que ele instancia, gerencia por uso e faz a injeção de dependência dos objetos de forma automática. Vamos ver como isso é feito na prática.

Usando o gerenciamento Spring

O Spring é uma ferramenta bem inteligente, mas ela ainda precisa de nós para indicarmos para ela o que gerenciar. E isto é feito de forma bem prática, usando anotações, veja só:

Utilizar a anotação @Component é uma forma de indicarmos que nossa classe é um Bean. Você já deve saber que antes de utilizar um objeto você precisa referencia-lo na memoria. No exemplo temos que a classe VacinaService tem como parâmetro uma Vacina, que será implementada da Interface Vacina. O Spring identifica a dependência de injeção e já efetua de forma automática em tempo de execução.

Agora utilizando uma aplicação Spring, a classe main é alterado para inicializar a aplicação Spring, com uso de SpringApplication.run, esta questão de inicialização deve abordada em um artigo futuro com a profundidade apropriada.
Para identificar a classe é necessário a anotação @SpringBootApplication.

Um boa forma de precaver erros é a utilização da anotação @ComponentScan, com nome autoexplicativo, é responsável por escanear a aplicação em busca dos Beans para gerenciar, para isso é necessário informar o package base onde encontra-se a aplicação. Portanto, a classe Application básica, vai se parece com algo assim:

package com.gomes.daniel.vacina;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = "com.gomes.daniel")
public class VacinaApi2Application {

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

}

Enter fullscreen mode Exit fullscreen mode

Pontos de Injeção

Primeiramente, pontos de injeção são os locais na aplicação onde o Spring coloca as dependências de um Bean, por exemplo, a instanciação na construção do objeto.
Se tivermos utilizando de polimorfismo e um objeto tiver mais de um construtor, sendo nenhum deles um construtor sem parâmetros, como o Spring sabe qual ele deve utilizar?

Neste caso que entra mais uma anotação o @Autowired, ela indica ao Spring, onde fazer a injeção dentro do Bean. Imagine que a nossa classe VacinaService tenha mais de um construtor, sem a anotação, o Spring iria apresentar um erro de execução ao executar o código abaixo:


package com.gomes.daniel.di.infrastructure.repository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.gomes.daniel.di.domain.repository.Vacina;
import com.gomes.daniel.di.model.Pessoa;

@Component
public class VacinaService {

    private Vacina vacina;

    @Autowired
    public VacinaService(Vacina vacina) {
        this.vacina = vacina;
        System.out.println("Teste VacinaService");
    }

    public VacinaService(int quantidade) {
        //Alguma inicialização
    }

    public VacinaService(String[] locais) {
        //Alguma inicialização
    }


    public void ReceberVacina(Pessoa pessoa) {
        vacina.vacinar(pessoa);
    }
}


Enter fullscreen mode Exit fullscreen mode

Ambiguidade de Beans

Ambiguidade de Bean é algo muito comum em aplicações, visto que são consequencia da inversão de controle, como o Spring faz as injeções de maneira automática, é necessário que ele precise de uma indicação de qual Bean utilizar em casos de ambiguidade. Este problema ocorre em casos como o do projeto da Vacina, onde nosso VacinaService recebe um objeto Vacina, e temos VacinaPfizer e VacinaAstraZeneca, o que ocorre? Isso mesmo, erro na inicialização. Aí que entra nossa ultima anotação básica deste artigo a @Primary, responsável por indicar ao Spring qual seu Bean responsável como de injeção.
Necessário ressaltar que existem métodos com melhores práticas que este, porém isso vale um artigo próprio e detalhado sobre, neste caso simplificado temos então como ficaria a classe VacinaAstraZeneca como primária.

package com.gomes.daniel.di.infrastructure.repository.vacinas;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.gomes.daniel.di.domain.repository.Vacina;
import com.gomes.daniel.di.model.Pessoa;

@Primary
@Component
public class VacinaAstraZeneca implements Vacina {


    public void vacinar(Pessoa pessoa) {
        System.out.printf("%s tem %d anos e recebeu a primeira dose da vacina, aguarde 15 dias para a segunda dose!\n", 
                    pessoa.getNome(),
                    pessoa.getIdade());
        System.out.println("Seu sistema imunologico recebeu uma versao enfraquecida do virus");
    }

}
Enter fullscreen mode Exit fullscreen mode

Espero que este guia rápido tenha te ajudado a ter uma leve percepção da utilização, o objetivo era elaborar um guia simples para pincelar parte uma pequena parte desta ferramenta tão vasta, este artigo faz parte do desafio Especialista Spring REST da AlgaWorks. Obrigado pela leitura. Daniel Gomes

Top comments (0)