Skip to content

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:

Image

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

Image

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

Image

L'esecuzione del metodo [demo] del test NUnit produce i risultati mostrati sopra ed elencati di seguito:

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

Commenti:

  • riga 2: il test inizia con l'esecuzione del metodo dell'attributo <Setup()>
  • riga 3: l'operazione
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)

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.

init personne [Simon,40]
  • Riga 4: L'operazione
            Console.WriteLine("personne1=" + personne1.ToString())

ha visualizzato il valore dell'oggetto [Person] creato.

personne1=[Simon,40]
  • Righe 5-6: La stessa cosa accade per il bean chiave [person2].
init personne [Brigitte,20]
personne2=[Brigitte,20]
  • riga 7: l'ultima operazione
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())

ha prodotto una sola riga di output:

personne2=[Brigitte,20]

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
            the singletons are destroyed
            ' on détruit les singletons
            factory.Dispose()

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:

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

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 metodo destroy** 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:

Image

L'esecuzione del test NUnit produce i seguenti risultati:

Image

Commentiamo i risultati visualizzati sullo schermo:

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

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>
  1. Il tag <list> consente di inizializzare un campo di tipo array o di tipo IList con valori diversi.
  1. 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.

Image

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

Image

Commenti:

***** 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
  • 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]