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: