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étodoclose**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:

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

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

A execução do método [demo] do teste NUnit apresenta os resultados indicados acima e reproduzidos abaixo:
Comentários:
- linha 2: o teste inicia com a execução do método de atributo <Setup()>
- linha 3: a operação
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.
- linha 4: a operação
fez com que fosse exibido o valor do objeto [Personne] criado.
- linhas 5-6: o mesmo fenómeno repete-se para o bean de chave [personne2].
- linha 7: a última operação
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
resultou apenas numa linha de exibição:
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
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:
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étododestroy**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:

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

Vamos comentar as mensagens apresentadas no ecrã:
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>
- A baliza <list> permite inicializar um campo do tipo tabela ou do tipo IList com diferentes valores.
- 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.

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

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