Skip to content

5. Spring IoC na prática

Vamos agora abordar, com exemplos, a aplicação prática do que vimos anteriormente.

5.1. Exemplo 1

A classe [Personne.vb] é a seguinte:

Namespace istia.st.springioc.demos
    Public Class Personne

         ' campos privados
        Private _nom As String
        Private _age As Integer

         ' construtor por predefinição
        Public Sub New()
        End Sub

         ' propriedades associadas aos campos privados
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                _nom = Value
            End Set
        End Property

        Public Property age() As Integer
            Get
                Return _age
            End Get
            Set(ByVal Value As Integer)
                _age = Value
            End Set
        End Property

         ' cadeia de identidade
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1}]", nom, age)
        End Function

         ' método init
        Public Sub init()
            Console.WriteLine("init personne {0}", Me.ToString)
        End Sub

         ' método close
        Public Sub close()
            Console.WriteLine("destroy personne {0}", Me.ToString)
        End Sub

    End Class
End Namespace

A classe apresenta:

  • dois campos privados «nome» e «idade», acessíveis através de propriedades
  • um método toString para recuperar o valor do objeto [Personne] na forma de uma cadeia de caracteres
  • um método init** que será chamado pelo Spring aquando da criação do objeto, um método close** que será chamado aquando da destruição do objeto

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

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
</objects>

Este ficheiro

  • define dois objetos de chaves, respetivamente «pessoa1» e «pessoa2», do tipo [Personne]
  • inicializa as propriedades [nom, age] de cada pessoa
  • define os métodos a serem chamados durante a construção inicial do objeto [init-method] e durante a destruição do objeto [destroy-method]

Para os nossos testes, utilizaremos classes de teste NUnit. A primeira será a seguinte:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo1
         ' o objeto a testar
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' cria-se uma instância da fábrica
            factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
             ' registo
            Console.WriteLine("setup test")
        End Sub

        <Test()> _
        Public Sub demo()
             ' recuperação, através da respetiva chave, dos objetos [Personne] do ficheiro Spring
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)
            Console.WriteLine("personne1=" + personne1.ToString())
            Dim personne2 As Personne = CType(factory.GetObject("personne2"), Personne)
            Console.WriteLine("personne2=" + personne2.ToString())
            personne2 = CType(factory.GetObject("personne2"), Personne)
            Console.WriteLine("personne2=" + personne2.ToString())
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' destruem-se os singletons
            factory.Dispose()
             ' libera-se a recurso da fábrica
            factory = Nothing
             ' acompanhamento
            Console.WriteLine("teardown test")
        End Sub

    End Class
End Namespace

Comentários:

  • para obter os objetos definidos no ficheiro [spring-config-1.xml], utilizamos um objeto do tipo [XmlObjectFactory]. Existem outros tipos de «factory» que permitem aceder aos singletons do ficheiro de configuração. O objeto [XmlObjectFactory] é obtido no método de atributo [SetUp] da classe de teste e armazenado numa variável privada. Recorde-se que o método dotado do atributo <Setup()> é executado antes de cada teste.
  • O ficheiro [spring-config-1.xml] será colocado na pasta [bin] da aplicação
  • O Spring pode utilizar ficheiros de configuração em vários formatos. O objeto [XmlObjectFactory] permite analisar um ficheiro de configuração no formato XML.
  • A análise de um ficheiro Spring resulta num objeto do tipo [XmlObjectFactory], neste caso o objeto «factory». Com este objeto, obtém-se um singleton identificado pela chave C através de factory.getObject(C).
  • O método [demo] solicita e apresenta o valor dos singletons com as chaves «pessoa1» e «pessoa2».

A estrutura do projeto do Visual Studio é a seguinte:

Image

Comentários:

  • o projeto VS chama-se [demo1]
  • a pasta [istia] contém os códigos-fonte. Os códigos compilados serão colocados na pasta [bin], dentro de DLL e [demo1.dll]:

Image

  • O ficheiro [spring-config-1.xml] encontra-se na pasta [bin] do projeto.
  • A pasta [bin] contém os ficheiros necessários para a aplicação:
    • Spring.Core.dll, Spring.Core.xml para as classes Spring
    • log4net.dll para os registos do Spring
    • demo1.dll, que é o ficheiro DLL produzido pela geração do projeto

O ficheiro DLL [demo1.dll], gerado durante a criação do projeto, é carregado na ferramenta de teste gráfico [Nunit-Gui 2.2]. Esta ferramenta apresenta automaticamente as classes NUnit presentes no ficheiro DLL:

Image

A execução do método [demo] do teste NUnit apresenta os resultados indicados acima e reproduzidos abaixo:

***** istia.st.springioc.tests.NunitTestSpringIocDemo1.demo
setup test
init personne [Simon,40]
personne1=[Simon,40]
init personne [Brigitte,20]
personne2=[Brigitte,20]
personne2=[Brigitte,20]
destroy personne [Simon,40]
destroy personne [Brigitte,20]
teardown test

Comentários:

  • linha 2: o teste inicia com a execução do método de atributo <Setup()>
  • linha 3: a operação
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)

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

init personne [Simon,40]
  • linha 4: a operação
            Console.WriteLine("personne1=" + personne1.ToString())

fez com que fosse exibido o valor do objeto [Personne] criado.

personne1=[Simon,40]
  • linhas 5-6: o mesmo fenómeno repete-se para o bean de chave [personne2].
init personne [Brigitte,20]
personne2=[Brigitte,20]
  • linha 7: a última operação
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())

resultou apenas numa linha de exibição:

personne2=[Brigitte,20]

Portanto, não provocou a criação de um novo objeto do tipo [Personne]. Se assim fosse, teríamos a exibição do método [init], o que não acontece neste caso. 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 ao mesmo. Se o objeto já tiver sido criado, o Spring limita-se a fornecer uma referência ao mesmo.

  • linhas 8-10: o método de atributo <TearDown()> é executado (linha 10). No seu interior, a operação
             ' destruem-se os singletons
            factory.Dispose()

provoca a destruição dos singletons criados pelo Spring. Isto faz com que, para cada um deles, seja executado o respetivo método [close], porque, no ficheiro de configuração, tinha sido escrito, para cada um deles: «destroy-method=close». É isso que indicam as mensagens:

destroy personne [Simon,40]
destroy personne [Brigitte,20]

Agora que já dominamos os fundamentos de uma configuração Spring, as nossas explicações serão um pouco mais rápidas daqui em diante.

5.2. Exemplo 2

Consideremos a seguinte nova classe [Voiture]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class Voiture
         ' campos privados
        Private _marque As String
        Private _type As String
        Private _propriétaire As Personne

         ' propriedades públicas
        Public Property marque() As String
            Get
                Return _marque
            End Get
            Set(ByVal Value As String)
                _marque = Value
            End Set
        End Property

        Public Property type() As String
            Get
                Return _type
            End Get
            Set(ByVal Value As String)
                _type = Value
            End Set
        End Property

        Public Property propriétaire() As Personne
            Get
                Return _propriétaire
            End Get
            Set(ByVal Value As Personne)
                _propriétaire = Value
            End Set
        End Property

         ' construtor por predefinição
        Public Sub New()

        End Sub

         ' construtor com três parâmetros
        Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
             ' inicializam-se os campos privados através das respetivas propriedades associadas
            With Me
                .marque = marque
                .type = type
                .propriétaire = propriétaire
            End With
        End Sub

         ' cadeia de identificação do objeto Carro
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
        End Function

         ' métodos init-destroy
        Public Sub init()
            Console.WriteLine("init voiture {0}", Me.ToString)
        End Sub

        Public Sub destroy()
            Console.WriteLine("destroy voiture {0}", Me.ToString)
        End Sub

    End Class
End Namespace

A classe apresenta:

  • três campos privados _type, _marque e _propriétaire. Estes campos podem ser inicializados e lidos através de propriedades públicas. Podem também ser inicializados utilizando o construtor Carro(String, String, Pessoa). A classe possui ainda um construtor sem argumentos que permite criar primeiro um objeto [Voiture] não inicializado e, em seguida, inicializá-lo através das suas propriedades públicas.
  • um método toString para recuperar o valor do objeto [Voiture] na forma de uma cadeia de caracteres
  • um método init** que será chamado pelo Spring logo após a criação do objeto, um método destroy** que será chamado quando o objeto for destruído

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

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- pessoas -->
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
     <!-- um carro -->
    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2"/>
        </constructor-arg>
    </object>
</objects>

Este ficheiro adiciona às definições anteriores um objeto de chave «carro1» do tipo [Voiture]. Para inicializar este objeto, poderia ter-se escrito:

    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2"/>
        </constructor-arg>
    </object>

Em vez de optar por este método já apresentado, optámos aqui por utilizar o construtor Carro(String, String, Pessoa) da classe. Além disso, o singleton [voiture1] define o método a ser chamado durante a construção inicial do objeto [init-method] e aquele a ser chamado durante a destruição do objeto [destroy-method].

Para os nossos testes, utilizaremos a seguinte classe de teste NUnit [NunitTestSpringIocDemo2]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo2
         ' a fábrica de singletons
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' cria-se uma instância da fábrica
            factory = New XmlObjectFactory(New FileStream("spring-config-2.xml", FileMode.Open))
             ' registo
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
    Public Sub destroy()
             ' destruímos os singletons
            factory.Dispose()
             ' liberta-se a fábrica
            factory = Nothing
             ' acompanhamento
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
        Public Sub demo2()
             ' recuperação do singleton [voiture1]
            Dim voiture1 As Voiture = CType(factory.GetObject("voiture1"), Voiture)
            Console.WriteLine("Voiture1=[{0}]", voiture1)
        End Sub

    End Class
End Namespace

Comentários:

  • os métodos de atributos <Setup()> e <TearDown()> permanecem inalterados.
  • O método [demo2] obtém uma referência ao singleton [voiture1] e apresenta-o.

A estrutura do projeto do Visual Studio permanece a mesma de antes. Surgiram apenas duas novas classes, bem como um ficheiro de configuração do Spring:

Image

A execução do teste NUnit apresenta os seguintes resultados:

Image

Vamos comentar as mensagens apresentadas no ecrã:

1
2
3
4
5
6
7
8
***** istia.st.springioc.tests.NunitTestSpringIocDemo2.demo2
setup test
init personne [Brigitte,20]
init voiture [Peugeot,307,[Brigitte,20]]
Voiture1=[[Peugeot,307,[Brigitte,20]]]
destroy voiture [Peugeot,307,[Brigitte,20]]
destroy personne [Brigitte,20]
teardown test

Comentários:

  • linha 2: método de atributo [Setup] executado antes de cada teste, neste caso [demo]
  • linha 3: o Spring inicia a criação do singleton [voiture1]. Este tem uma dependência do singleton [personne2], que não existe. Este último é, portanto, criado e o seu método [init] é executado.
  • linha 4: o singleton [voiture1] pode agora ser criado. O seu método [init] é então executado.
  • linha 5: o método [demo2] exibe o valor do singleton [voiture1]
  • linhas 6-8: o método [TearDown] do teste é executado. Os singletons são destruídos, o que leva à execução dos seus métodos [destroy]

5.3. Exemplo 3

Introduzimos a seguinte nova classe [GroupePersonnes]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class GroupePersonnes
         ' campos privados
        Private _membres() As Personne
        Private _groupesDeTravail As Hashtable

         ' propriedades públicas
        Public Property membres() As Personne()
            Get
                Return _membres
            End Get
            Set(ByVal Value() As Personne)
                _membres = Value
            End Set
        End Property

        Public Property groupesDeTravail() As Hashtable
            Get
                Return _groupesDeTravail
            End Get
            Set(ByVal Value As Hashtable)
                _groupesDeTravail = Value
            End Set
        End Property

         ' construtor por predefinição
        Public Sub New()

        End Sub

         ' cadeia de identidade do objeto GroupeDePersonnes
        Public Overrides Function tostring() As String
             ' percorre-se a lista de membros do grupo
            Dim identité As String = "[membres=("
            Dim i As Integer
            For i = 0 To membres.Length - 2
                identité += membres(i).ToString + ","
            Next
            identité += membres(i).ToString + "), groupes de travail=("
             ' percorre-se o dicionário de grupos de trabalho
            Dim clés As IEnumerator = groupesDeTravail.Keys.GetEnumerator
            Dim clé As Object
            While clés.MoveNext
                clé = clés.Current
                identité += String.Format("[{0},{1}] ", clé, groupesDeTravail(clé))
            End While
             ' retorna o resultado
            Return identité + "]"
        End Function

         ' métodos init-destroy
        Public Sub init()
            Console.WriteLine("init GroupeDePersonnes {0}", Me.ToString)
        End Sub

        Public Sub destroy()
            Console.WriteLine("destroy GroupeDePersonnes {0}", Me.ToString)
        End Sub

    End Class
End Namespace

Os seus dois membros privados são:

_membres: uma tabela com os membros do grupo

_groupesDeTravail: um dicionário que atribui uma pessoa a um grupo de trabalho

Estes membros privados são disponibilizados através de propriedades públicas. O objetivo aqui é mostrar como o Spring permite inicializar objetos complexos, tais como objetos que possuem campos do tipo tabela ou dicionário.

O ficheiro de configuração do Spring [spring-config-3.xml] será o seguinte:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- pessoas -->
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
     <!-- um carro -->
    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2" />
        </constructor-arg>
    </object>
     <!-- um grupo de pessoas -->
    <object id="groupe1" type="istia.st.springioc.demos.GroupePersonnes" init-method="init"
        destroy-method="destroy">
        <property name="membres">
            <list>
                <ref object="personne1" />
                <ref object="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <dictionary>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </dictionary>
        </property>
    </object>
</objects>
  1. A baliza <list> permite inicializar um campo do tipo tabela ou do tipo IList com diferentes valores.
  1. A baliza <dictionary> permite fazer o mesmo com um campo que implemente a interface IDictionary

Para os nossos testes, utilizaremos a seguinte classe de teste NUnit [NunitTestSpringIocDemo3]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo3
         ' o objeto a testar
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' cria-se uma instância da fábrica
            factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
             ' registo
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' destruímos os singletons
            factory.Dispose()
             ' acompanhamento
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
    Public Sub demo3()
             ' recuperação do singleton [groupe1]
            Dim groupe1 As GroupePersonnes = CType(factory.GetObject("groupe1"), GroupePersonnes)
            Console.WriteLine("groupe1={0}", groupe1)
        End Sub

    End Class
End Namespace

O método [demo3] recupera o singleton [groupe1] e apresenta-o.

A estrutura do projeto do Visual Studio permanece a mesma de antes, com a exceção de que agora tem duas classes e um ficheiro de configuração adicionais.

Image

A execução do método [demo3] do teste NUnit produz os seguintes resultados:

Image

Comentários:

***** istia.st.springioc.tests.NunitTestSpringIocDemo3.demo3
setup test
init personne [Simon,40]
init personne [Brigitte,20]
init GroupeDePersonnes [membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
groupe1=[membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
destroy GroupeDePersonnes [membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
destroy personne [Simon,40]
destroy personne [Brigitte,20]
teardown test
  • linha 2: método de atributo [Setup] executado antes de cada teste, neste caso o [demo]
  • linhas 3-4: o Spring inicia a criação do singleton [groupe1]. Este tem uma dependência dos singletons [personne1, personne2], que não existem. Estes últimos são criados e os seus métodos [init] são executados.
  • linha 5: o singleton [groupe1] pode agora ser criado. O seu método [init] é então executado.
  • linha 6: o método [demo3] exibe o valor do singleton [groupe1]
  • linhas 7-10: o método [TearDown] do teste é executado. Os singletons são destruídos, o que leva à execução dos seus métodos [destroy]