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:

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]:

- 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:

A execução do método [demo] do teste NUnit produz os resultados apresentados acima e listados abaixo:
Comentários:
- linha 2: o teste começa com a execução do método do atributo <Setup()>
- linha 3: a operação
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.
- Linha 4: A operação
exibiu o valor do objeto [Person] criado.
- Linhas 5-6: O mesmo acontece com o bean-chave [person2].
- 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:
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
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:
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:

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

Vamos comentar os resultados apresentados no ecrã:
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>
- A tag <list> permite-lhe inicializar um campo do tipo array ou do tipo IList com valores diferentes.
- 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.

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

Comentários:
- 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]