Implementação de IoC no Spring: Controle de Inversão, Injeção de Dependência e Configuração XML

Implementação de IoC no Spring: Múltiplos Métodos

I. Fundamentos do IoC

1.1. Conceitos:

1. IoC - Controle de Inversão (Inversion of Control)

IoC é um conceito de design.

2. DI - Injeção de Dependência

A injeção de dependência é um método para implementar IoC. Alguns consideram DI apenas outra forma de IoC.

Em programas sem IoC, a criação de objetos e as relações de dependência entre eles são totalmente codificadas no programa. A criação de objetos é controlada pelo próprio programa. Com o controle de inversão, a criação de objetos é transferida para um terceiro. O que chamamos de controle de inversão é: a maneira de obter objetos dependentes é invertida.

IoC é o conteúdo central do Spring Framework, que implementa perfeitamente IoC de várias maneiras, usando configuração XML, anotações ou a nova versão do Spring que permite implementar IoC sem configuração. O contêiner Spring, durante a inicialização, lê primeiro o arquivo de configuração, cria objetos com base no arquivo de configuração ou metadados e os armazena no contêiner. Quando o programa é executado, os objetos necessários são retirados do contêiner IoC.

Ao usar a configuração XML para configurar Beans, as informações de definição do Bean estão separadas da implementação. Usando anotações, podemos unir as duas. As informações de definição do Bean são definidas diretamente na classe de implementação através de anotações, alcançando assim o objetivo de configuração zero.

II. Implementando IoC usando Configuração XML

II.0. Criando um projeto Maven

II.1. Configuração do pom.xml do projeto Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>br.exemplo.spring</groupId>
    <artifactId>projetoSpring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>SpringIoC</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>5.3.10</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>
</project>

II.2. Abordagem Padrão

II.2.1. IAutorRepositorio.java

package br.exemplo.spring;

/**Interface de acesso a dados de autores*/
public interface IAutorRepositorio {
    /**Adiciona novo autor*/
    void adicionar(String nome);
}

II.2.2. RepositorioMySQLAutor.java

package br.exemplo.spring;
/**Implementação da interface*/
public class RepositorioMySQLAutor implements IAutorRepositorio {
    public void adicionar(String nome){
        System.out.println("Autor "+nome+" adicionado ao banco MySQL com sucesso!");
    }
}

II.2.3. RepositorioPostgreSQLAutor.java

package br.exemplo.spring;
/**Implementação da interface*/
public class RepositorioPostgreSQLAutor implements IAutorRepositorio {
    public void adicionar(String nome){
        System.out.println("Autor "+nome+" adicionado ao banco PostgreSQL com sucesso!");
    }
}

II.2.4. ServicoAutor.java

package br.exemplo.spring;

public class ServicoAutor {
    IAutorRepositorio autorRepositorio;
    public ServicoAutor(){
       autorRepositorio = new RepositorioMySQLAutor();
    }
    /**Adiciona novo autor*/
    public void novoAutor(){
        String nomeAutor="Guia de Aprendizado Spring MVC";
        autorRepositorio.adicionar(nomeAutor);
    }
}

II.2.5. Classe de teste ServicoAutorTeste.java

package br.exemplo.spring;

import org.junit.Test;

/**Teste do serviço de autor sem arquivo de configuração*/
public class ServicoAutorTeste {
    ServicoAutor servico = new ServicoAutor();
    @Test
    public void novoAutor() throws Exception {
        servico.novoAutor();
    }
}

II.3. Criando objetos usando construtor sem parâmetros

II.3.1. IAutorRepositorio.java

package br.exemplo.spring;

/**Interface de acesso a dados de autores*/
public interface IAutorRepositorio {
    /**Adiciona novo autor*/
    void adicionar(String nome);
}

II.3.2. RepositorioMySQLAutor.java

package br.exemplo.spring;
/**Implementação da interface IAutorRepositorio*/
public class RepositorioMySQLAutor implements IAutorRepositorio {
    /**Salva um autor no banco MySQL*/
    public void adicionar(String nome){
        System.out.println("Autor "+nome+" adicionado ao banco MySQL com sucesso!");
    }
}

II.3.3. RepositorioPostgreSQLAutor.java

package br.exemplo.spring;

/**Implementação da interface IAutorRepositorio*/
public class RepositorioPostgreSQLAutor implements IAutorRepositorio {
    /**Salva um autor no banco PostgreSQL*/
    public void adicionar(String nome){
        System.out.println("Autor "+nome+" adicionado ao banco PostgreSQL com sucesso!");
    }
}

II.3.4. ServicoAutor.java

package br.exemplo.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ServicoAutor {
    /**Cria uma propriedade do tipo IAutorRepositorio*/
    IAutorRepositorio autorRepositorio;
    
    /**Usa construtor sem parâmetros para obter o contêiner*/
    public ServicoAutor(){
        //Cria contêiner
        ApplicationContext ctx=new ClassPathXmlApplicationContext("configuracaoIoC.xml");
        /*
            Obtém o bean com id de "repositorioAutor" do contêiner
            "repositorioAutor" vem do valor do atributo id no arquivo de configuração
        */
        autorRepositorio = (IAutorRepositorio) ctx.getBean("repositorioAutor");
    }
    /**Adiciona novo autor*/
    public void novoAutor(){
        String nomeAutor="Guia de Aprendizado Spring MVC";
        /**Salva o autor*/
        autorRepositorio.adicionar(nomeAutor);
    }
}

II.3.5. Classe de teste ServicoAutorTeste.java

package br.exemplo.spring;

import org.junit.Test;

/**Usa arquivo de configuração para decidir qual banco de dados usar*/
public class ServicoAutorTeste {
    ServicoAutor servico = new ServicoAutor();
    @Test
    public void novoAutor() throws Exception {
        servico.novoAutor();
    }
}

II.3.6. configuracaoIoC.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--Importa a implementação da interface de acesso a dados usando PostgreSQL para este arquivo de configuração (contêiner)-->
    <!--Usa construtor sem parâmetros-->
    <bean id="repositorioAutor" class="br.exemplo.spring.RepositorioPostgreSQLAutor"></bean>
</beans>

II.4. Criando objetos usando construtor com parâmetros

II.4.1. Pessoa.java

package br.exemplo.spring;
/**Classe base para pessoas*/
public abstract class Pessoa {
    /**Nome da pessoa*/
    public String nome;
}

II.4.2. Aluno.java

package br.exemplo.spring;

/**Classe de aluno*/
public class Aluno extends Pessoa{
    /**Altura do aluno*/
    public int altura;
    /**Construtor com parâmetros*/
    public Aluno(String nome,int altura){
        this.nome=nome;
        this.altura=altura;
    }
    @Override
    public String toString() {
        return "Aluno{" +
                "nome='" + nome + '\'' +
                ", altura=" + altura +
                '}';
    }
}

II.4.3. Escola.java

package br.exemplo.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Escola {
    public static void main(String[] args) {
        //contêiner ioc
        ApplicationContext ctx  =
                new ClassPathXmlApplicationContext("configuracaoAluno.xml","configuracaoIoC.xml");
        //Obtém objeto do contêiner
        Pessoa joao = ctx.getBean("joao",Pessoa.class);
        System.out.println(joao);
    }
}

II.4.4. configuracaoAluno.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="joao" class="br.exemplo.spring.Aluno">
        <constructor-arg name="nome" value="João Silva"></constructor-arg>
        <constructor-arg name="altura" value="175"></constructor-arg>
    </bean>
</beans>

II.5. Criando objetos usando construtor com parâmetros (continuação)

II.5.1. Endereco.java

package br.exemplo.spring;

/**Classe de endereço*/
public class Endereco {
    /**País*/
    private String pais;
    /**Cidade */
    private String cidade;

    public Endereco() {
    }

    public Endereco(String pais, String cidade) {
        this.pais = pais;
        this.cidade = cidade;
    }

    public String getPais() {
        return pais;
    }

    public void setPais(String pais) {
        this.pais = pais;
    }

    public String getCidade() {
        return cidade;
    }

    public void setCidade(String cidade) {
        this.cidade = cidade;
    }

    @Override
    public String toString() {
        return "Endereco{" +
                "pais='" + pais + '\'' +
                ", cidade='" + cidade + '\'' +
                '}';
    }
}

II.5.2. configuracaoEndereco.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--Se não quiser usar nomes de parâmetro no construtor, pode usar índices (não recomendado)-->
    <bean id="maria" class="br.exemplo.spring.Aluno">
        <constructor-arg index="0" value="Maria Souza"></constructor-arg>
        <constructor-arg index="1" value="168"></constructor-arg>
    </bean>
</beans>

II.6. Criando objetos usando atribuição de propriedades

II.6.1. configuracaoCompleta.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--Atribuição via propriedades-->
    <bean id="carlos" class="br.exemplo.spring.Aluno">
        <constructor-arg index="0" value="Carlos Pereira"></constructor-arg>
        <constructor-arg index="1" value="180"></constructor-arg>
    </bean>
    <bean name="saoPaulo" class="br.exemplo.spring.Endereco">
        <property name="pais" value="Brasil"></property>
        <property name="cidade" value="São Paulo"></property>
    </bean>
</beans>

II.7. Criando objetos usando referências a objetos

II.7.1. Pessoa.java (atualizado)

package br.exemplo.spring;

/**Classe base para pessoas*/
public abstract class Pessoa {
    /**Nome da pessoa*/
    public String nome;
    /**Endereço da pessoa*/
    public Endereco endereco;
}

II.7.2. Aluno.java (atualizado)

package br.exemplo.spring;

/**Classe de aluno*/
public class Aluno extends Pessoa {
    /**Altura do aluno*/
    public int altura;
    /**Construtor com parâmetros*/
    public Aluno(String nome,int altura){
        this.nome=nome;
        this.altura=altura;
    }
    /**Construtor com parâmetros incluindo endereço*/
    public Aluno(String nome,int altura,Endereco endereco){
        this.nome=nome;
        this.altura=altura;
        this.endereco=endereco;
    }
    @Override
    public String toString() {
        return "Aluno{" +
                "nome='" + nome + '\'' +
                ", altura=" + altura +
                '}'+endereco;
    }
}

II.7.3. configuracaoReferencia.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--Pode usar id ou (e) atributo name para especificar identificadores de bean-->
    <bean name="rio" class="br.exemplo.spring.Endereco">
        <property name="pais" value="Brasil"></property>
        <property name="cidade" value="Rio de Janeiro"></property>
    </bean>
    <bean id="ana" class="br.exemplo.spring.Aluno">
        <constructor-arg name="nome" value="Ana Costa"></constructor-arg>
        <constructor-arg name="altura" value="165"></constructor-arg>
        <constructor-arg name="endereco" ref="rio"></constructor-arg>
    </bean>
</beans>

II.8. Escopo de objetos - O atributo scope pode especificar o escopo

II.8.1. Por padrão, os objetos recuperados do contêiner são singletons:

Modifique o código da classe Escola.java para verificar o padrão singleton:

package br.exemplo.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Escola {
    public static void main(String[] args) {
        /**contêiner ioc*/
        ApplicationContext ctx  =
                new ClassPathXmlApplicationContext("configuracaoReferencia.xml","configuracaoIoC.xml");
        //Obtém objeto do contêiner
        Aluno ana1 = ctx.getBean("ana",Aluno.class);
        Aluno ana2 = ctx.getBean("ana",Aluno.class);
        
        System.out.println(ana1==ana2);//Saída será true, confirmando padrão singleton
        System.out.println(ana1!=ana2);
    }
}

II.8.2. Escopo (use o atributo scope para especificar o escopo)

Exemplo de definição de múltiplas instâncias de Bean com scope="prototype":

1. Modifique o conteúdo do arquivo configuracaoReferencia.xml para:

    <bean id="ana" class="br.exemplo.spring.Aluno" scope="prototype">
        <constructor-arg name="nome" value="Ana Costa"></constructor-arg>
        <constructor-arg name="altura" value="165"></constructor-arg>
        <constructor-arg name="endereco" ref="rio"></constructor-arg>
    </bean>

2. Modifique o código da classe Escola.java para:

package br.exemplo.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Escola {
    public static void main(String[] args) {
        /**contêiner ioc*/
        ApplicationContext ctx  =
                new ClassPathXmlApplicationContext("configuracaoReferencia.xml","configuracaoIoC.xml");
          //Obtém objeto do contêiner
        Aluno pedro=ctx.getBean("pedro",Aluno.class);
        Aluno anaA=ctx.getBean("ana",Aluno.class);
        Aluno anaB=ctx.getBean("ana",Aluno.class);
        System.out.println(pedro);
        System.out.println(anaA==anaB);
    }
}

3. Resultado da execução: -- O resultado false indica que não é mais singleton, mas sim múltiplas instâncias.

II.9. Inicialização preguiçosa de Bean

O comportamento padrão da implementação ApplicationContext é instanciar todos os beans singleton antecipadamente durante a inicialização. Normalmente, essa intsanciação antecipada é boa, pois erros de configuração ou ambiente são descobertos imediatamente. Se você não desejar isso, pode definir um bean singleton como carregamento preguiçoso para evitar sua instanciação antecipaad. A inicialização preguiçosa informa ao contêiner IoC para instanciar apenas na primeira necessidade, não durante a inicialização do contêiner. No arquivo de configuração XML, a inicialização preguiçosa é controlada pelo atrbiuto lazy-init do elemento <bean/>, por exemplo:

<!--Carregamento preguiçoso-->
<bean id="preguiçoso" class="com.foo.BeanCaroDeCriar" lazy-init="true"/>
<!--Não carregamento preguiçoso-->
<bean name="nao.preguiçoso" class="com.foo.OutroBean"/>

II.10. Métodos de callback

II.10.1. Funções de callback de inicialização

Arquivo de configuração: -- Modifique o arquivo de configuração para o seguinte:

    <bean id="pedro" class="br.exemplo.spring.Aluno" init-method="inicializar" destroy-method="finalizar">
        <constructor-arg name="nome" value="Pedro Santos"></constructor-arg>
        <constructor-arg name="altura" value="172"></constructor-arg>
        <constructor-arg name="endereco" ref="rio"></constructor-arg>
    </bean>

Classe Aluno: -- Modifique a classe Aluno para:

package br.exemplo.spring;

/**Classe de aluno*/
public class Aluno extends Pessoa {
    /**Altura do aluno*/
    public int altura;
    /**Construtor com parâmetros*/
    public Aluno(String nome,int altura){
        this.nome=nome;
        this.altura=altura;
    }
    /**Construtor com parâmetros incluindo endereço*/
    public Aluno(String nome,int altura,Endereco endereco){
        this.nome=nome;
        this.altura=altura;
        this.endereco=endereco;
    }
    @Override
    public String toString() {
        return "Aluno{" + "altura=" + altura+",nome="+nome +'}'+endereco;
    }

    public void inicializar(){
        System.out.println("Objeto criado");
    }
    public void finalizar(){
        System.out.println("Objeto sendo reciclado");
    }
}

Resultado da execução:

Tags: Spring IoC DI XML Injeção de Dependência

Publicado em 7-8 00:36