5. Spring IoC nella pratica
Ora useremo degli esempi per mettere in pratica ciò che abbiamo visto in precedenza.
5.1. Esempio 1
La classe [Person.vb] è la seguente:
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
La classe contiene:
- due campi privati, nome ed età, accessibili tramite proprietà
- un metodo toString per recuperare il valore dell'oggetto [Person] come stringa
- un metodo init che verrà chiamato da Spring quando l'oggetto viene creato, e un metodo close che verrà chiamato quando l'oggetto viene distrutto
Per creare oggetti di tipo [Person], useremo il seguente file 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>
Questo file
- definisce due oggetti con le rispettive chiavi "person1" e "person2" di tipo [Person]
- inizializza le proprietà [name, age] per ciascuna persona
- definisce i metodi da chiamare durante la costruzione iniziale dell'oggetto [init-method] e durante la distruzione dell'oggetto [destroy-method]
Per i nostri test, useremo le classi di test NUnit. La prima sarà la seguente:
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
Commenti:
- Per recuperare gli oggetti definiti nel file [spring-config-1.xml], utilizziamo un oggetto di tipo [XmlObjectFactory]. Esistono altri tipi di "factory" che consentono l'accesso ai singleton nel file di configurazione. L'oggetto [XmlObjectFactory] viene ottenuto nel metodo con l'attributo [SetUp] della classe di test e memorizzato in una variabile privata. Si noti che il metodo con l'attributo <Setup()> viene eseguito prima di ogni test.
- Il file [spring-config-1.xml] verrà inserito nella cartella [bin] dell'applicazione
- Spring può utilizzare file di configurazione in vari formati. L'oggetto [XmlObjectFactory] viene utilizzato per analizzare un file di configurazione in formato XML.
- La lettura di un file Spring restituisce un oggetto di tipo [XmlObjectFactory], in questo caso l'oggetto
factory. Utilizzando questo oggetto, è possibile recuperare un singleton identificato dalla chiave**C**utilizzando**factory.getObject(C)**. - Il metodo [demo] recupera e visualizza i valori dei singleton con le chiavi "person1" e "person2".
La struttura del progetto Visual Studio è la seguente:

Commenti:
- Il progetto VS si chiama [demo1]
- La cartella [istia] contiene il codice sorgente. Il codice compilato verrà inserito nella cartella [bin] nel file DLL [demo1.dll]:

- Il file [spring-config-1.xml] si trova nella cartella [bin] del progetto.
- La cartella [bin] contiene i file richiesti dall'applicazione:
- Spring.Core.dll, Spring.Core.xml per le classi Spring
- log4net.dll per i log di Spring
- demo1.dll, che è la DLL generata dal progetto
La DLL [demo1.dll] generata dal progetto viene caricata nello strumento di test grafico [Nunit-Gui 2.2]. Questo strumento visualizza automaticamente le classi Nunit presenti nella DLL:

L'esecuzione del metodo [demo] del test NUnit produce i risultati mostrati sopra ed elencati di seguito:
Commenti:
- riga 2: il test inizia con l'esecuzione del metodo dell'attributo <Setup()>
- riga 3: l'operazione
ha forzato la creazione del bean [personne1]. Poiché nella definizione del singleton [personne1] avevamo scritto [init-method="init"], è stato eseguito il metodo [init] dell'oggetto [Person] creato. Viene visualizzato il messaggio corrispondente.
- Riga 4: L'operazione
ha visualizzato il valore dell'oggetto [Person] creato.
- Righe 5-6: La stessa cosa accade per il bean chiave [person2].
- riga 7: l'ultima operazione
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
ha prodotto una sola riga di output:
Pertanto, non ha causato la creazione di un nuovo oggetto di tipo [Person]. Se così fosse stato, sarebbe stato visualizzato il metodo [init], cosa che qui non è avvenuta. Questo è il principio del singleton. Per impostazione predefinita, Spring crea una sola istanza dei bean nel suo file di configurazione. Si tratta di un servizio di riferimento agli oggetti. Se gli viene richiesto il riferimento a un oggetto che non è ancora stato creato, lo crea e restituisce un riferimento ad esso. Se l'oggetto è già stato creato, Spring restituisce semplicemente un riferimento ad esso.
- Righe 8–10: Viene eseguito il metodo dell'attributo <TearDown()> (riga 10). Al suo interno, l'operazione
provoca la distruzione dei singleton creati da Spring. Ciò fa sì che ciascuno di essi esegua il proprio metodo [close] poiché, nel file di configurazione, avevamo scritto per ciascuno di essi: "destroy-method=close". Questo è ciò che indicano i display:
Ora che abbiamo trattato le basi della configurazione di Spring, potremo procedere un po' più velocemente nelle nostre spiegazioni.
5.2. Esempio 2
Consideriamo la seguente nuova 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
La classe ha:
- tre campi privati: _type, _make e _owner. Questi campi possono essere inizializzati e letti tramite proprietà pubbliche. Possono anche essere inizializzati utilizzando il costruttore Car(String, String, Person). La classe dispone inoltre di un costruttore senza argomenti che consente di creare prima un oggetto [Car] non inizializzato e poi inizializzarlo tramite le sue proprietà pubbliche.
- un metodo toString per recuperare il valore dell'oggetto [Car] come stringa
- un metodo
init**che verrà chiamato da Spring subito dopo la creazione dell'oggetto, un metododestroy** che verrà chiamato quando l'oggetto viene distrutto
Per creare oggetti di tipo [Car], useremo il seguente file 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>
<!-- 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>
Questo file aggiunge un oggetto con la chiave "car1" di tipo [Car] alle definizioni precedenti. Per inizializzare questo oggetto, avremmo potuto scrivere:
<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>
Invece di scegliere il metodo già presentato, abbiamo scelto qui di utilizzare il costruttore Car(String, String, Person) della classe. Inoltre, il singleton [car1] definisce il metodo da chiamare durante la costruzione iniziale dell'oggetto [init-method] e il metodo da chiamare durante la distruzione dell'oggetto [destroy-method].
Per i nostri test, useremo la seguente classe di test 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
Commenti:
- I metodi degli attributi <Setup()> e <TearDown()> rimangono invariati.
- Il metodo [demo2] recupera un riferimento al singleton [car1] e lo visualizza.
La struttura del progetto Visual Studio rimane la stessa di prima. Sono state aggiunte solo due nuove classi, insieme a un file di configurazione Spring:

L'esecuzione del test NUnit produce i seguenti risultati:

Commentiamo i risultati visualizzati sullo schermo:
Commenti:
- riga 2: metodo dell'attributo [Setup] eseguito prima di ogni test, qui [demo]
- riga 3: Spring inizia a creare il singleton [car1]. Questo singleton ha una dipendenza dal singleton [person2], che non esiste ancora. Quest'ultimo viene quindi creato e viene eseguito il suo metodo [init].
- riga 4: ora è possibile creare il singleton [car1]. Viene quindi eseguito il suo metodo [init].
- Riga 5: il metodo [demo2] visualizza il valore del singleton [car1]
- Righe 6–8: viene eseguito il metodo [TearDown] del test. I singleton vengono distrutti, innescando l'esecuzione dei loro metodi [destroy]
5.3. Esempio 3
Introduciamo la seguente nuova 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
I suoi due membri privati sono:
_members: un array di persone che sono membri del gruppo
_workGroups: un dizionario che associa una persona a un gruppo di lavoro
Questi membri privati sono resi accessibili tramite proprietà pubbliche. L'obiettivo qui è dimostrare come Spring consenta di inizializzare oggetti complessi, come quelli con campi array o dizionario.
Il file di configurazione di Spring [spring-config-3.xml] sarà il seguente:
<?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>
- Il tag <list> consente di inizializzare un campo di tipo array o di tipo IList con valori diversi.
- Il tag <dictionary> consente di fare lo stesso con un campo che implementa l'interfaccia IDictionary
Per i nostri test, useremo la seguente classe di test 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
Il metodo [demo3] recupera il singleton [group1] e lo visualizza.
La struttura del progetto Visual Studio rimane la stessa di prima, tranne per il fatto che ora include due classi aggiuntive e un file di configurazione.

L'esecuzione del metodo [demo3] nel test NUnit produce i seguenti risultati:

Commenti:
- riga 2: metodo dell'attributo [Setup] eseguito prima di ogni test, qui [demo]
- righe 3-4: Spring inizia a creare il singleton [group1]. Questo singleton dipende dai singleton [person1, person2], che non esistono ancora. Questi singleton vengono creati e i loro metodi [init] vengono eseguiti.
- riga 5: ora è possibile creare il singleton [group1]. Viene quindi eseguito il suo metodo [init].
- riga 6: il metodo [demo3] visualizza il valore del singleton [group1]
- Righe 7–10: viene eseguito il metodo [TearDown] del test. I singleton vengono distrutti, innescando l'esecuzione dei loro metodi [destroy]