Skip to content

5. Spring IoC na prática

Vamos agora utilizar exemplos para pôr em prática o que vimos anteriormente.

5.1. Exemplo 1

A classe [Person.vb] é a seguinte:

Namespace istia.st.springioc.demos
    Public Class Personne

        ' private fields
        Private _nom As String
        Private _age As Integer

        ' default builder
        Public Sub New()
        End Sub

        ' properties associated with private fields
        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

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

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

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

    End Class
End Namespace

A classe contém:

  • dois campos privados, nome e idade, acessíveis através de propriedades
  • um método toString para recuperar o valor do objeto [Person] como uma string
  • um método init que será chamado pelo Spring quando o objeto for criado, e um método close que será chamado quando o objeto for destruído

Para criar objetos do tipo [Person], 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 com as respetivas chaves «person1» e «person2» do tipo [Person]
  • inicializa as propriedades [nome, idade] para 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
        ' the test object
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
            ' create a factory instance
            factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
            ' log
            Console.WriteLine("setup test")
        End Sub

        <Test()> _
        Public Sub demo()
            ' retrieve [Person] objects from the Spring file using their keys
            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()
            ' we destroy the singletons
            factory.Dispose()
            ' release the factory resource
            factory = Nothing
            ' follow-up
            Console.WriteLine("teardown test")
        End Sub

    End Class
End Namespace

Comentários:

  • Para recuperar os objetos definidos no ficheiro [spring-config-1.xml], utilizamos um objeto do tipo [XmlObjectFactory]. Existem outros tipos de «fábrica» que permitem o acesso aos singletons no ficheiro de configuração. O objeto [XmlObjectFactory] é obtido no método com o atributo [SetUp] da classe de teste e armazenado numa variável privada. Note-se que o método com o 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] é utilizado para analisar um ficheiro de configuração no formato XML.
  • A leitura de um ficheiro Spring devolve um objeto do tipo [XmlObjectFactory], neste caso o objeto factory. Utilizando este objeto, um singleton identificado pela chave **C** pode ser recuperado utilizando **factory.getObject(C)**.
  • O método [demo] recupera e apresenta os valores dos singletons com as chaves "person1" e "person2".

A estrutura do projeto do Visual Studio é a seguinte:

Image

Comentários:

  • O projeto VS tem o nome [demo1]
  • A pasta [istia] contém o código-fonte. O código compilado será colocado na pasta [bin] na DLL [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 à aplicação:
    • Spring.Core.dll, Spring.Core.xml para as classes Spring
    • log4net.dll para registos Spring
    • demo1.dll, que é a DLL gerada pelo projeto

A DLL [demo1.dll] gerada pelo projeto é carregada na ferramenta de testes gráficos [Nunit-Gui 2.2]. Esta ferramenta apresenta automaticamente as classes Nunit presentes na DLL:

Image

A execução do método [demo] do teste NUnit produz os resultados apresentados acima e listados 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 começa com a execução do método do atributo <Setup()>
  • linha 3: a operação
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)

forçou a criação do bean [person1]. Como na definição do singleton [person1] tínhamos escrito [init-method="init"], o método [init] do objeto [Person] criado foi executado. A mensagem correspondente é exibida.

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

exibiu o valor do objeto [Person] criado.

personne1=[Simon,40]
  • Linhas 5-6: O mesmo acontece com o bean-chave [person2].
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 em apenas uma linha de saída:

personne2=[Brigitte,20]

Portanto, não provocou a criação de um novo objeto do tipo [Person]. Se assim fosse, o método [init] teria sido exibido, o que não é o caso aqui. Este é o princípio do singleton. Por predefinição, o Spring cria apenas uma única instância dos beans no seu ficheiro de configuração. Trata-se de um serviço de referência a objetos. Se lhe for solicitada a referência a um objeto que ainda não tenha sido criado, ele cria-o e devolve uma referência ao mesmo. Se o objeto já tiver sido criado, o Spring devolve simplesmente uma referência ao mesmo.

  • Linhas 8–10: O método de atributo <TearDown()> é executado (linha 10). No seu interior, a operação
            the singletons are destroyed
            ' on détruit les singletons
            factory.Dispose()

provoca a destruição dos singletons criados pelo Spring. Isto faz com que cada um deles execute o seu método [close], porque escrevemos, no ficheiro de configuração, para cada um deles: "destroy-method=close". É isto que os ecrãs indicam:

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

Agora que abordámos os conceitos básicos da configuração do Spring, poderemos avançar um pouco mais rapidamente nas nossas explicações.

5.2. Exemplo 2

Considere a seguinte nova classe [Car]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class Voiture
        ' private fields
        Private _marque As String
        Private _type As String
        Private _propriétaire As Personne

        ' public property
        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

        ' default builder
        Public Sub New()

        End Sub

        ' three-parameter constructor
        Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
            ' initialize private fields via their associated properties
            With Me
                .marque = marque
                .type = type
                .propriétaire = propriétaire
            End With
        End Sub

        ' car object identity string
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
        End Function

        ' init-destroy methods
        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 tem:

  • três campos privados: _type, _make e _owner. Estes campos podem ser inicializados e lidos através de propriedades públicas. Também podem ser inicializados utilizando o construtor Car(String, String, Person). A classe possui ainda um construtor sem argumentos que permite criar primeiro um objeto [Car] 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 [Car] como uma string
  • um método init que será chamado pelo Spring imediatamente após a criação do objeto, e um método destroy que será chamado quando o objeto for destruído

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

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- people -->
    <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>
     <!-- a car -->
    <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 um objeto com a chave "car1" do tipo [Car] às definições anteriores. Para inicializar este objeto, poderíamos ter 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 escolher o método já apresentado, optámos aqui por utilizar o construtor Car(String, String, Person) da classe. Além disso, o singleton [car1] define o método a ser chamado durante a construção inicial do objeto [init-method] e o método 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
        ' the singletons factory
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
            ' create a factory instance
            factory = New XmlObjectFactory(New FileStream("spring-config-2.xml", FileMode.Open))
            ' log
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
    Public Sub destroy()
            ' we destroy the singletons
            factory.Dispose()
            ' we free the factory
            factory = Nothing
            ' follow-up
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
        Public Sub demo2()
            ' singleton retrieval [car1]]
            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] recupera uma referência ao singleton [car1] e apresenta-a.

A estrutura do projeto do Visual Studio permanece a mesma de antes. Apenas duas novas classes foram adicionadas, juntamente com um ficheiro de configuração do Spring:

Image

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

Image

Vamos comentar os resultados apresentados 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, aqui [demo]
  • linha 3: o Spring começa a criar o singleton [car1]. Este singleton tem uma dependência do singleton [person2], que ainda não existe. Este último é, portanto, criado e o seu método [init] executado.
  • linha 4: o singleton [car1] pode agora ser criado. O seu método [init] é então executado.
  • Linha 5: O método [demo2] exibe o valor do singleton [car1]
  • Linhas 6–8: O método [TearDown] do teste é executado. Os singletons são destruídos, desencadeando a execução dos seus métodos [destroy]

5.3. Exemplo 3

Apresentamos a seguinte nova classe [PersonGroup]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class GroupePersonnes
        ' private fields
        Private _membres() As Personne
        Private _groupesDeTravail As Hashtable

        ' public property
        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

        ' default builder
        Public Sub New()

        End Sub

        ' object identity string GroupeDePersonnes
        Public Overrides Function tostring() As String
            ' browse the list of group members
            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=("
            ' browse the workgroup dictionary
            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
            ' we return the result
            Return identité + "]"
        End Function

        ' init-destroy methods
        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:

_members: uma matriz de pessoas que são membros do grupo

_workGroups: um dicionário que associa uma pessoa a um grupo de trabalho

Estes membros privados são disponibilizados através de propriedades públicas. O objetivo aqui é demonstrar como o Spring permite inicializar objetos complexos, tais como aqueles com campos de matriz 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>
     <!-- people -->
    <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>
     <!-- a car -->
    <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>
     <!-- a group of people -->
    <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 tag <list> permite-lhe inicializar um campo do tipo array ou do tipo IList com valores diferentes.
  1. A tag <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
        ' the test object
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
            ' create a factory instance
            factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
            ' log
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
        Public Sub destroy()
            ' we destroy the singletons
            factory.Dispose()
            ' follow-up
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
    Public Sub demo3()
            ' retrieving the singleton [group1]
            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 [group1] e apresenta-o.

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

Image

A execução do método [demo3] no 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, aqui [demo]
  • linhas 3-4: O Spring começa a criar o singleton [group1]. Este singleton depende dos singletons [person1, person2], que ainda não existem. Estes singletons são criados e os seus métodos [init] são executados.
  • linha 5: o singleton [group1] pode agora ser criado. O seu método [init] é então executado.
  • linha 6: o método [demo3] exibe o valor do singleton [group1]
  • Linhas 7–10: O método [TearDown] do teste é executado. Os singletons são destruídos, desencadeando a execução dos seus métodos [destroy]