Skip to content

15. Spring IoC

15.1. Introduction

Propomos explorar as possibilidades de configuração e integração do framework Spring (http://www.springframework.org), bem como definir e utilizar o conceito de IoC (Inversão de Controlo), também conhecido como injeção de dependências (Dependency Injection)

Consideremos a aplicação de três camadas que acabámos de construir:

Para responder aos pedidos do utilizador, o controlador [Application] tem de se dirigir à camada [service]. No nosso exemplo, esta era uma instância do tipo [DaoImpl]. O controlador [Application] obteve uma referência à camada [service] no seu método [init] (parágrafo 14.8.3):

@SuppressWarnings("serial")
public class Application extends HttpServlet {
...

     // serviço
    ServiceImpl service=null;
...
     // inicialização
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
         // instanciação da camada [dao]
        DaoImpl dao = new DaoImpl();
        dao.init();
         // instanciação da camada [service]
        service = new ServiceImpl();
        service.setDao(dao);
    }
  • linha 6: a camada [dao] foi instanciada através da criação explícita de uma instância [DaoImpl]
  • linha 9: a camada [service] foi instanciada através da criação explícita de uma instância [ServiceImpl]

Recorde-se que as classes [DaoImpl] e [ServiceImpl] implementam interfaces, respetivamente as interfaces [IDao] e [IService]. Em versões futuras, a interface [IDao] será implementada por uma classe que gere uma lista de pessoas armazenada numa base de dados. Chamemos a esta classe [DaoBD], para efeitos de exemplo. Substituir a implementação [DaoImpl] da camada [dao] pela implementação [DaoBD] exigirá uma recompilação da camada [web]. Com efeito, a linha 6 acima, que instancia a camada [dao] com um tipo [DaoImpl], deve agora instanciá-la com um tipo [DaoBD]. A nossa camada [web] depende, portanto, da camada [dao]. A linha 9 acima mostra que também depende da camada [service].

O Spring IoC vai permitir-nos criar uma aplicação de três camadas em que as camadas são independentes umas das outras, c.a.d, de modo que alterar uma não implica a alteração das outras. Isto proporciona uma grande flexibilidade na evolução da aplicação.

A arquitetura anterior irá evoluir da seguinte forma:

Com o [Spring IoC], o controlador [Application] irá obter a referência de que necessita na camada [service] da seguinte forma:

  1. no seu método [init], irá solicitar à camada [Spring IoC] que lhe forneça uma referência na camada [service]
  2. O [Spring IoC] irá então utilizar um ficheiro de configuração XML que lhe indica qual a classe que deve ser instanciada e como deve ser inicializada.
  3. [Spring IoC] devolve ao controlador [Application] a referência da camada [service] criada.

A vantagem desta solução é que, a partir de agora, os nomes das classes que instanciam as diferentes camadas já não estão codificados de forma rígida no método [init] do controlador, mas sim simplesmente presentes num ficheiro de configuração. Alterar a implementação de uma camada implicará uma alteração neste ficheiro de configuração, mas não no controlador.

Vamos agora apresentar as possibilidades do [Spring IoC] através de exemplos.

15.2. Spring IoC na prática

15.2.1. Spring

O [Spring IoC] faz parte de um projeto mais vasto disponível na URL [http://www.springframework.org/] (maio de 2006):

  • [1]: O Spring utiliza várias tecnologias de terceiros, aqui designadas por dépendances. É necessário descarregar a versão com o dépendances para evitar ter de descarregar posteriormente as bibliotecas das ferramentas de terceiros.
  • [2]: a estrutura de diretórios do ficheiro compactado descarregado
  • [3]: a distribuição [Spring], c.a.d. Os arquivos .jar do próprio projeto Spring, sem as suas dependências. A aparência do Spring ([IoC]) é assegurada pelos arquivos [spring-core.jar, spring-beans.jar].
  • [4,5]: os arquivos das ferramentas de terceiros

15.2.2. Projetos Eclipse dos exemplos

Vamos criar três exemplos que ilustram a utilização do Spring IoC. Todos eles farão parte do seguinte projeto Eclipse:

Image

O projeto [springioc-exemples] está configurado para que os ficheiros-fonte e as classes compiladas se encontrem na raiz da pasta do projeto:

  • [1]: a estrutura de pastas do projeto [Eclipse]
  • [2]: os ficheiros de configuração do Spring encontram-se na raiz do projeto, ou seja, na pasta Classpath da aplicação
  • [3]: as classes do exemplo 1
  • [4]: as classes do exemplo 2
  • [5]: as classes do exemplo 3
  • [6]: as bibliotecas do projeto [spring-core.jar, spring-beans.jar] encontram-se na pasta [dist] da distribuição do Spring e as do projeto [commons-logging.jar] encontram-se na pasta [lib/jakarta-commons]. Estes três arquivos foram incluídos no Classpath da aplicação.

15.2.3. Exemplo 1

Os elementos do exemplo 1 foram colocados no pacote [springioc01] do projeto:

Image

A classe [Personne] é a seguinte:

package istia.st.springioc01;

public class Personne {

     // características
    private String nom;
    private int age;

     // visualização de Pessoa
    public String toString() {
        return "nom=[" + this.nom + "], age=[" + this.age + "]";
    }

     // inicialização-encerramento
    public void init() {
        System.out.println("init personne [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy personne [" + this.toString() + "]");
    }

     // getters-setters
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

}

A classe apresenta:

  • linhas 6-7: dois campos privados «nom» e «age»
  • linhas 23-38: os métodos de leitura (get) e de gravação (set) destes dois campos
  • linhas 10-12: um método toString para recuperar o valor do objeto [Personne] na forma de uma cadeia de caracteres
  • linhas 15-21: um método `init` que será chamado pelo Spring aquando da criação do objeto, e um método `close` que será chamado aquando da destruição do objeto

Para instanciar objetos do tipo [Personne] utilizando o Spring, iremos utilizar o seguinte ficheiro [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
</beans>
  • linhas 3 e 12: a baliza <beans> é a baliza raiz dos ficheiros de configuração do Spring. Dentro desta baliza, a baliza <bean> serve para definir os diferentes objetos a criar.
  • linhas 4-7: definição de um bean
  • linha 4: o bean chama-se [personne1] (atributo id) e é uma instância da classe [istia.st.springioc01.Personne] (atributo class). O método [init] da instância será chamado assim que esta for criada (atributo init-method) e o método [close] da instância será chamado antes da sua destruição (atributo destroy-method).
  • linha 5: definem o valor a atribuir à propriedade [nom] (atributo name) da instância [Personne] criada. Para efetuar esta inicialização, o Spring utilizará o método [setNom]. Por isso, é necessário que este método exista. É o que acontece neste caso.
  • linha 6: o mesmo se aplica à propriedade [age].
  • linhas 8-11: definição análoga de um bean denominado [personne2]

A classe de teste [Main] é a seguinte:

package istia.st.springioc01;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
         // utilização do ficheiro de configuração do Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
         // recuperação do bean [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
         // recuperação do bean [personne2]
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         // recuperação do bean [personne2] mais uma vez
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         // eliminam-se todos os beans
        bf.destroySingletons();
    }
}

Comentários:

  • linha 10: para obter os beans definidos no ficheiro [spring-config-01.xml], utilizamos um objeto do tipo [XmlBeanFactory] que permite instanciar os beans definidos num ficheiro XML. O ficheiro [spring-config-01.xml] será colocado no [ClassPath] da aplicação, c.a.d, num dos diretórios explorados pela máquina virtual Java quando procura uma classe referenciada pela aplicação. O objeto [ClassPathResource] serve para procurar um recurso no [ClassPath] de uma aplicação, neste caso o ficheiro [spring-config-01.xml]. O objeto [bf] obtido (Bean Factory) permite obter a referência de um bean denominado «XX» através da instrução bf.getBean("XX").
  • linha 12: solicita-se uma referência ao bean denominado [personne1] no ficheiro [spring-config-01.xml].
  • linha 13: é apresentado o valor do objeto [Personne] correspondente.
  • linhas 15-16: faz-se o mesmo para o bean denominado [personne2].
  • linhas 18-19: solicita-se novamente o bean denominado [personne2].
  • linha 21: eliminam-se todos os beans [bf] e c.a.d, bem como os criados a partir do ficheiro [spring-config-01.xml].

A execução da classe [Main] produz os seguintes resultados:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Comentários:

  • a linha 1 foi obtida através da execução da linha 12 de [Main]. A operação
Personne personne1 = (Personne) bf.getBean("personne1");

forçou a criação do bean [personne1]. Como na definição do bean [personne1] se tinha escrito [init-method="init"], foi executado o método [init] do objeto [Personne] criado. A mensagem correspondente é apresentada.

  • linha 2: a linha 13 de [Main] fez com que fosse exibido o valor do objeto [Personne] criado.
  • linhas 3-4: o mesmo fenómeno repete-se para o bean denominado [personne2].
  • linha 5: a operação das linhas 18-19 de [Main]
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

não provocou a criação de um novo objeto do tipo [Personne]. Se assim fosse, teríamos visto a exibição do método [init]. Este é o princípio do singleton. Por predefinição, o Spring cria apenas uma única instância dos beans do seu ficheiro de configuração. Trata-se de um serviço de referências a objetos. Se lhe for solicitada a referência de um objeto ainda não criado, ele cria-o e devolve uma referência. Se o objeto já tiver sido criado, o Spring limita-se a fornecer uma referência. Neste caso, como o [personne2] já tinha sido criado, o Spring limita-se a devolver uma referência.

  • As exibições nas linhas 6-7 foram provocadas pela linha 21 de [Main], que solicita a destruição de todos os beans referenciados pelo objeto [XmlBeanFactory bf], ou seja, os beans [personne1, personne2]. Como estes dois beans possuem o atributo [destroy-method="close"], o método [close] de ambos os beans é executado e provoca a exibição das linhas 6-7.

Agora que já dominamos os conceitos básicos de uma configuração Spring, as nossas explicações serão um pouco mais rápidas.

15.2.4. Exemplo 2

Os elementos do exemplo 2 estão localizados no pacote [springioc02] do projeto:

Image

O pacote [springioc02] é obtido, em primeiro lugar, através de copiar/colar do pacote [springioc01]; em seguida, adiciona-se a classe [Voiture] e adapta-se a classe [Main] ao novo exemplo.

A classe [Voiture] é a seguinte:

package istia.st.springioc02;

public class Voiture {
     // características
    private String marque;
    private String type;
    private Personne propriétaire;

     // construtores
    public Voiture() {
    }

    public Voiture(String marque, String type, Personne propriétaire) {
        setMarque(marque);
        setType(type);
        setPropriétaire(propriétaire);
    }

     // toString
    public String toString() {
        return "Voiture : marque=[" + this.marque + "] type=[" + this.type
                + "] propriétaire=[" + this.propriétaire + "]";
    }

     // getters-setters
    public String getMarque() {
        return marque;
    }

    public void setMarque(String marque) {
        this.marque = marque;
    }

    public Personne getPropriétaire() {
        return propriétaire;
    }

    public void setPropriétaire(Personne propriétaire) {
        this.propriétaire = propriétaire;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

     // inicialização e encerramento
    public void init() {
        System.out.println("init voiture [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy voiture [" + this.toString() + "]");
    }
}

A classe apresenta:

  • linhas 5-7: três campos privados «tipo», «marca» e «proprietário». Estes campos podem ser inicializados e lidos através dos métodos públicos «get» e «set» dos beans, nas linhas 26-48. Podem também ser inicializados utilizando o construtor «Carro(String, String, Pessoa)», definido nas linhas 13-17. A classe possui ainda um construtor sem argumentos, de modo a cumprir a norma JavaBean.
  • linhas 20-23: um método toString para recuperar o valor do objeto [Voiture] na forma de uma cadeia de caracteres
  • linhas 51-57: um método init que será chamado pelo Spring logo após a criação do objeto, e um método close que será chamado quando o objeto for destruído

Para criar objetos do tipo [Voiture], utilizaremos o seguinte ficheiro Spring [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="voiture1" class="istia.st.springioc02.Voiture" init-method="init" destroy-method="close">
        <constructor-arg index="0" value="Peugeot" />
        <constructor-arg index="1" value="307" />
        <constructor-arg index="2">
            <ref local="personne2" />
        </constructor-arg>
    </bean>
</beans>

Este ficheiro adiciona aos beans definidos em [spring-config-01.xml] um bean com a chave «voiture1» do tipo [Voiture] (linhas 12-17). Para inicializar este bean, poderíamos ter escrito:

1
2
3
4
5
6
7
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" init-method="init" destroy-method="close">
        <property name="marque" value="Peugeot"/>
        <property name="type" value="307"/>
        <property name="propriétaire">
            <ref local="personne2"/>
        </property>
</bean>

Em vez de escolher este método já apresentado no exemplo 1, optámos por utilizar aqui o construtor Carro(String, String, Pessoa) da classe.

  • linha 12: definição do nome do bean, da sua classe, do método a executar após a sua instanciação e do método a executar após a sua eliminação.
  • linha 13: valor do primeiro parâmetro do construtor [Voiture(String, String, Personne)].
  • linha 14: valor do segundo parâmetro do construtor [Voiture(String, String, Personne)].
  • linhas 15-17: valor do terceiro parâmetro do construtor [Voiture(String, String, Personne)]. Este parâmetro é do tipo [Personne]. É-lhe fornecido como valor a referência (etiqueta ref) do bean [personne2] definido no mesmo ficheiro (atributo local).

Para os nossos testes, utilizaremos a seguinte classe [Main]:

package istia.st.springioc02;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
         // utilização do ficheiro de configuração do Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
         // recuperação do bean [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
         // eliminar os beans
        bf.destroySingletons();
    }
}

O método [main] solicita a referência do bean [voiture1] (linha 12) e apresenta-a (linha 13). Os resultados são os seguintes:

1
2
3
4
5
init personne [nom=[Brigitte], age=[20]]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]
destroy voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
destroy personne [nom=[Brigitte], age=[20]]

Comentários:

  1. o método [main] solicita uma referência ao bean [voiture1] (linha 12). O Spring inicia a criação do bean [voiture1], uma vez que este bean ainda não foi criado (singleton). Como o bean [voiture1] faz referência ao bean [personne2], este último bean é, por sua vez, criado. O bean [personne2] foi criado. O seu método [init] é então executado (linha 1) dos resultados. O bean [voiture1] é, em seguida, instanciado. O seu método [init] é então executado (linha 2) dos resultados.
  2. A linha 3 dos resultados provém da linha 13 de [main]: o valor do bean [voiture1] é apresentado.
  3. A linha 15 de [main] solicita a destruição de todos os beans existentes, o que provoca a exibição das linhas 4 e 5 dos resultados.

15.2.5. Exemplo 3

Os elementos do exemplo 3 estão colocados no pacote [springioc03] do projeto:

Image

O pacote [springioc03] é obtido, em primeiro lugar, através de copiar/colar do pacote [springioc01] e, em seguida, adiciona-se a classe [GroupePersonnes]; elimina-se a classe [Voiture] e adapta-se a classe [Main] ao novo exemplo.

A classe [GroupePersonnes] é a seguinte:

package istia.st.springioc03;

import java.util.Map;

public class GroupePersonnes {

     // características
    private Personne[] membres;
    private Map groupesDeTravail;

     // getters - setters
    public Personne[] getMembres() {
        return membres;
    }

    public void setMembres(Personne[] membres) {
        this.membres = membres;
    }

    public Map getGroupesDeTravail() {
        return groupesDeTravail;
    }

    public void setGroupesDeTravail(Map groupesDeTravail) {
        this.groupesDeTravail = groupesDeTravail;
    }

     // exibição
    public String toString() {
        String liste = "membres : ";
        for (int i = 0; i < this.membres.length; i++) {
            liste += "[" + this.membres[i].toString() + "]";
        }
        return liste + ", groupes de travail = "
                + this.groupesDeTravail.toString();
    }

     // inicialização e encerramento
    public void init() {
        System.out.println("init GroupePersonnes [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
    }
}

Os seus dois membros privados são:

linha 8: membros: um tabuleiro com as pessoas que integram o grupo

linha 9: groupesDeTravail: um dicionário que atribui uma pessoa a um grupo de trabalho

Note-se aqui que a classe [GroupePersonnes] não define um construtor sem argumentos. Recorde-se que, na ausência de qualquer construtor, existe um construtor «padrão», que é o construtor sem argumentos e que não faz nada.

O objetivo aqui é mostrar como o Spring permite inicializar objetos complexos, tais como objetos que possuem campos do tipo matriz ou dicionário. O ficheiro de beans [spring-config-03.xml] do exemplo 3 é o seguinte:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="groupe1" class="istia.st.springioc03.GroupePersonnes" init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref local="personne1" />
                <ref local="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte" value="Marketing" />
                <entry key="Simon" value="Ressources humaines" />
            </map>
        </property>
    </bean>
</beans>
  • linhas 14-17: a baliza <list> permite inicializar um campo do tipo tabela ou que implemente a interface List com diferentes valores.
  • linhas 20-23: a baliza <map> permite fazer o mesmo com um campo que implemente a interface Map.

Para os nossos testes, utilizaremos a seguinte classe [Main]:

package istia.st.springioc03;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
         // utilização do ficheiro de configuração do Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
         // recuperação do bean [groupe1]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
         // eliminação dos beans
        bf.destroySingletons();
    }
}
  • linhas 12-13: solicitamos ao Spring uma referência ao bean [groupe1] e exibimos o seu valor.

Os resultados obtidos são os seguintes:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}
destroy GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Comentários:

  • na linha 12 do [Main], solicita-se uma referência ao bean [groupe1]. O Spring inicia a criação deste bean. Como o bean [groupe1] faz referência aos beans [personne1] e [personne2], estes dois beans são criados (linhas 1 e 2) nos resultados. O bean [groupe1] é, em seguida, instanciado e o seu método [init] é executado (linha 3 dos resultados).
  • A linha 13 de [Main] exibe a linha 4 dos resultados.
  • A linha 15 de [Main] faz com que sejam exibidas as linhas 5 a 7 dos resultados.

15.3. Configuração de uma aplicação n-tier com o Spring

Consideremos uma aplicação de 3 camadas com a seguinte estrutura:

utilizador

Dados

Camada de negócios [metier]

Camada de acesso aos dados [dao]

Camada de interface do utilizador [ui]

Pretendemos demonstrar aqui a utilidade do Spring na construção de uma arquitetura deste tipo.

  • As três camadas serão tornadas independentes graças à utilização de interfaces Java
  • A integração das três camadas será realizada pelo Spring

A estrutura da aplicação no Eclipse poderá ser a seguinte:

  • [1]: a camada [dao]:
    • [IDao]: a interface da camada
    • [Dao1, Dao2]: duas implementações desta interface
  • [2]: a camada [metier]:
    • [IMetier]: a interface da camada
    • [Metier1, Metier2]: duas implementações desta interface
  • [3]: a camada [ui]:
    • [IUi]: a interface da camada
    • [Ui1, Ui2]: duas implementações desta interface
  • [4]: os ficheiros de configuração Spring da aplicação. Vamos configurar a aplicação de duas formas.
  • [5]: as bibliotecas necessárias para a aplicação. São as mesmas utilizadas nos exemplos anteriores.
  • [6]: o pacote de testes. O [Main1] utilizará a configuração [spring-config-01.xml] e o [Main2] utilizará a configuração [spring-config-02.xml].

O objetivo deste exemplo é mostrar que podemos alterar a implementação de uma ou mais camadas da aplicação sem qualquer impacto nas outras camadas. Tudo se passa no ficheiro de configuração do Spring.


A camada [dao]


A camada [dao] implementa a seguinte interface [IDao]:

1
2
3
4
5
package istia.st.springioc.troistier.dao;

public interface IDao {
    public int doSomethingInDaoLayer(int a, int b);
}

A implementação [Dao1] será a seguinte:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }
}

A implementação [Dao2] será a seguinte:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }
}

A camada [métier]


A camada [métier] implementa a seguinte interface [IMetier]:

1
2
3
4
5
package istia.st.springioc.troistier.metier;

public interface IMetier {
      public int doSomethingInBusinessLayer(int a, int b);
}

A implementação [Metier1] será a seguinte:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier1 implements IMetier {

     // camada [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

A implementação [Metier2] será a seguinte:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier2 implements IMetier {

     // camada [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a--;
        b--;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

A camada [ui]


A camada [ui] implementa a seguinte interface [IUi]:

1
2
3
4
5
package istia.st.springioc.troistier.ui;

public interface IUi {
    public int doSomethingInUiLayer(int a, int b);
}

A implementação [Ui1] será a seguinte:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui1 implements IUi {

     // camada [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

A implementação [Ui2] será a seguinte:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui2 implements IUi {

     // camada [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b--;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

Os ficheiros de configuração do Spring


O primeiro, [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- a classe dao -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
    <!-- a classe de negócio -->
    <bean id="metier"     class="istia.st.springioc.troistier.metier.Metier1">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- a classe UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui1">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

O segundo, [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- a classe DAO -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
    <!-- a classe de negócio -->
    <bean id="metier"
        class="istia.st.springioc.troistier.metier.Metier2">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- a classe UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

Os programas de teste


O programa [Main1] é o seguinte:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main1 {

    public static void main(String[] args) {
         // recupera-se uma implementação da interface IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
         // utiliza-se a classe
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // exibe-se o resultado
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

O programa [Main1] utiliza o ficheiro de configuração [spring-config-01.xml] e, por conseguinte, as implementações [Ui1, Metier1, Dao1] das camadas. Os resultados obtidos na consola do Eclipse:

ui(10,20)=34

O programa [Main2] é o seguinte:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main2 {

    public static void main(String[] args) {
         // recupera-se uma implementação da interface IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
         // utiliza-se a classe
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // exibe-se o resultado
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

O programa [Main2] utiliza o ficheiro de configuração [spring-config-02.xml] e, consequentemente, as implementações [Ui2, Metier2, Dao2] das camadas. Resultados obtidos na consola do Eclipse:

ui(10,20)=-10

15.4. Conclusion

A aplicação que criámos possui uma grande flexibilidade de evolução. É possível alterar a implementação de uma camada através de uma simples configuração. O código das outras camadas permanece inalterado. Isto é conseguido graças ao conceito IoC, que é um dos dois pilares do Spring. O outro pilar é o AOP (Programação Orientada a Aspectos), que ainda não apresentámos. Permite adicionar, também através de configuração, «comportamento» a um método de uma classe sem alterar o código da mesma.