Skip to content

5. Spring IoC par la pratique

Nous abordons maintenant avec des exemples, la mise en pratique de ce que nous avons vu précédemment.

5.1. Exemple 1

La classe [Personne.vb] est la suivante :

Namespace istia.st.springioc.demos
    Public Class Personne

        ' champs privés
        Private _nom As String
        Private _age As Integer

        ' constructeur par défaut
        Public Sub New()
        End Sub

        ' propriétés associées aux champs privés
        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

        ' chaîne d'identité
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1}]", nom, age)
        End Function

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

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

    End Class
End Namespace

La classe présente :

  • deux champs privés nom et age accessibles via des propriétés
  • une méthode toString pour récupérer la valeur de l'objet [Personne] sous la forme d'une chaîne de caractères
  • une méthode init qui sera appelée par Spring à la création de l'objet, une méthode close qui sera appelée à la destruction de l'objet

Pour créer des objets de type [Personne], nous utiliserons le fichier Spring [spring-config-1.xml] suivant :

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

Ce fichier

  • définit deux objets de clés respectives "personne1" et "personne2" de type [Personne]
  • il initialise les propriétés [nom, age] de chaque personne
  • il définit les méthodes à appeler lors de la construction initiale de l'objet [init-method] et lors de la destruction de l'objet [destroy-method]

Pour nos tests, nous utiliserons des classes de test NUnit. La première sera la suivante :

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
        ' l'objet à tester
        Private factory As XmlObjectFactory

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

        <Test()> _
        Public Sub demo()
            ' récupération par leur clé des objets [Personne] du fichier 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()
            ' on détruit les singletons
            factory.Dispose()
            ' on libère la ressource factory
            factory = Nothing
            ' suivi
            Console.WriteLine("teardown test")
        End Sub

    End Class
End Namespace

Commentaires :

  • pour obtenir les objets définis dans le fichier [spring-config-1.xml], nous utilisons un objet de type [XmlObjectFactory]. Il existe d'autres types de " factory " permettant d'accéder aux singletons du fichier de configuration. L'objet [XmlObjectFactory] est obtenu dans la méthode d'attribut [SetUp] de la classe de test et mémorisé dans une variable privée. Rappelons que la méthode dotée de l'attribut <Setup()> est exécutée avant chaque test.
  • le fichier [spring-config-1.xml] sera placé dans le dossier [bin] de l'application
  • Spring peut utiliser des fichiers de configuration ayant divers formats. L'objet [XmlObjectFactory] permet d'analyser un fichier de configuration au format XML.
  • l'exploitation d'un fichier Spring donne un objet de type [XmlObjectFactory], ici l'objet factory. Avec cet objet, un singleton identifié par la clé C, s'obtient par factory.getObject(C).
  • la méthode [demo] demande et affiche la valeur des singletons de clé "personne1" et "personne2".

La structure du projet Visual Studio est la suivante :

Image

Commentaires :

  • le projet VS s'appelle [demo1]
  • le dossier [istia] contient les codes source. Les codes compilés iront dans le dossier [bin] dans la DLL [demo1.dll] :

Image

  • le fichier [spring-config-1.xml] est dans le dossier [bin] du projet.
  • le dossier [bin] contient les fichiers nécessaires à l'application :
    • Spring.Core.dll, Spring.Core.xml pour les classes Spring
    • log4net.dll pour les logs de Spring
    • demo1.dll qui est la DLL produite par la génération du projet

La DLL [demo1.dll] produite par la génération du projet est chargée dans l'outil de test graphique [Nunit-Gui 2.2]. Celui-ci affiche automatiquement les classes Nunit présentes dans la DLL :

Image

L'exécution de la méthode [demo] du test NUnit donne les résultats indiqués ci-dessus et repris ci-dessous :

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

Commentaires :

  • ligne 2 : le test démarre avec l'excécution de la méthode d'attribut <Setup()>
  • ligne 3 : l'opération
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)

a forcé la création du bean [personne1]. Parce que dans la définition du singleton [personne1] on avait écrit [init-method="init"], la méthode [init] de l'objet [Personne] créé a été exécutée. Le message correspondant est affiché.

init personne [Simon,40]
  • ligne 4 : l'opération
            Console.WriteLine("personne1=" + personne1.ToString())

a fait afficher la valeur de l'objet [Personne] créé.

personne1=[Simon,40]
  • lignes 5-6 : le même phénomène se répète pour le bean de clé [personne2].
init personne [Brigitte,20]
personne2=[Brigitte,20]
  • ligne 7 : la dernière opération
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())

n'a donné qu'une ligne d'affichage :

personne2=[Brigitte,20]

Elle n'a donc pas provoqué la création d'un nouvel objet de type [Personne]. Si cela avait été le cas, on aurait eu l'affichage de la méthode [init], ce qui n'est pas le cas ici. C'est le principe du singleton. Spring, par défaut, ne crée qu'un seul exemplaire des beans de son fichier de configuration. C'est un service de références d'objet. Si on lui demande la référence d'un objet non encore créé, il le crée et en rend une référence. Si l'objet a déjà été créé, Spring se contente d'en donner une référence.

  • lignes 8-10 : la méthode d'attribut <TearDown()> s'exécute (ligne 10). Dedans, l'opération
            ' on détruit les singletons
            factory.Dispose()

provoque la destruction des singletons créés par Spring. Cela provoque pour chacun d'eux l'exécution de leur méthode [close] parce qu'on avait écrit, dans le fichier de configuration, pour chacun d'eux : " destroy-method=close ". C'est ce qu'indiquent les affichages :

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

Les bases d'une configuration Spring étant maintenant acquises, nous serons désormais un peu plus rapides dans nos explications.

5.2. Exemple 2

Considérons la nouvelle classe [Voiture] suivante :

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class Voiture
        ' champs privés
        Private _marque As String
        Private _type As String
        Private _propriétaire As Personne

        ' propriétés publiques
        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

        ' constructeur par défaut
        Public Sub New()

        End Sub

        ' constructeur à trois paramètres
        Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
            ' on initialise les champs privés via leurs propriétés associées
            With Me
                .marque = marque
                .type = type
                .propriétaire = propriétaire
            End With
        End Sub

        ' chaîne d'identité de l'objet Voiture
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
        End Function

        ' méthodes 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

La classe présente :

  • trois champs privés _type, _marque et _propriétaire. Ces champs peuvent être initialisés et lus par des propriétés publiques. Ils peuvent être également initialisés à l'aide du constructeur Voiture(String, String, Personne). La classe possède également un constructeur sans arguments qui permet de créer d'abord un objet [Voiture] non initialisé et de l'initialiser ensuite via ses propriétés publiques.
  • une méthode toString pour récupérer la valeur de l'objet [Voiture] sous la forme d'une chaîne de caractères
  • une méthode init qui sera appelée par Spring juste après la création de l'objet, une méthode destroy qui sera appelée à la destruction de l'objet

Pour créer des objets de type [Voiture], nous utiliserons le fichier Spring [spring-config-2.xml] suivant :

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
    <!-- des personnes -->
    <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>
    <!-- une voiture -->
    <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>

Ce fichier ajoute aux définitions précédentes un objet de clé "voiture1" de type [Voiture]. Pour initialiser cet objet, on aurait pu écrire :

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

Plutôt que de choisir cette méthode déjà présentée, nous avons choisi ici, d'utiliser le constructeur Voiture(String, String, Personne) de la classe. Par ailleurs, le singleton [voiture1] définit la méthode à appeler lors de la construction initiale de l'objet [init-method] et celle à appeler lors de la destruction de l'objet [destroy-method].

Pour nos tests, nous utiliserons la classe de test NUnit [NunitTestSpringIocDemo2] suivante :

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
        ' la fabrique de singletons
        Private factory As XmlObjectFactory

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

        <TearDown()> _
    Public Sub destroy()
            ' on détruit les singletons
            factory.Dispose()
            ' on libère le factory
            factory = Nothing
            ' suivi
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
        Public Sub demo2()
            ' récupération du singleton [voiture1]
            Dim voiture1 As Voiture = CType(factory.GetObject("voiture1"), Voiture)
            Console.WriteLine("Voiture1=[{0}]", voiture1)
        End Sub

    End Class
End Namespace

Commentaires :

  • les méthodes d'attributs <Setup()> et <TearDown()> restent inchangées.
  • la méthode [demo2] récupère une référence sur le singleton [voiture1] et affiche celui-ci.

La structure du projet Visual Studio reste ce qu'elle était auparavant. Seules deux nouvelles classes sont apparues ainsi qu'un fichier de configuration Spring :

Image

L'exécution du test NUnit donne les résultats suivants :

Image

Commentons les affichages écran :

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

Commentaires :

  • ligne 2 : méthode d'attribut [Setup] exécutée avant chaque test, ici [demo]
  • ligne 3 : Spring commence la création du singleton [voiture1]. Celui-ci a une dépendance vis à vis du singleton [personne2] qui n'existe pas. Ce dernier est donc créé et sa méthode [init] exécutée.
  • ligne 4 : le singleton [voiture1] peut maintenant être créé. Sa méthode [init] est alors exécutée.
  • ligne 5 : la méthode [demo2] fait afficher la valeur du singleton [voiture1]
  • lignes 6-8 : la méthode [TearDown] du test est exécutée. Les singletons sont détruits d'où l'exécution de leurs méthodes [destroy]

5.3. Exemple 3

Nous introduisons la nouvelle classe [GroupePersonnes] suivante :

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class GroupePersonnes
        ' champs privés
        Private _membres() As Personne
        Private _groupesDeTravail As Hashtable

        ' propriétés publiques
        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

        ' constructeur par défaut
        Public Sub New()

        End Sub

        ' chaîne d'identité de l'objet GroupeDePersonnes
        Public Overrides Function tostring() As String
            ' on parcourt la liste des membres du groupe
            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=("
            ' on parcourt le dictionnaire des groupes de travail
            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
            ' on rend le résultat
            Return identité + "]"
        End Function

        ' méthodes 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

Ses deux membres privés sont :

_membres : un tableau de personnes membres du groupe

_groupesDeTravail : un dictionnaire affectant une personne à un groupe de travail

Ces membres privés sont rendus accessibles via des propriétés publiques. On cherche ici, à montrer comment Spring permet d'initialiser des objets complexes tels que des objets possédant des champs de type tableau ou dictionnaire.

Le fichier Spring [spring-config-3.xml] de configuration sera le suivant :

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
    <!-- des personnes -->
    <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>
    <!-- une voiture -->
    <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>
    <!-- un groupe de personnes -->
    <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. la balise <list> permet d'initialiser avec différentes valeurs un champ de type tableau ou de type IList.
  1. la balise <dictionary> permet de faire la même chose avec un champ implémentant l'interface IDictionary

Pour nos tests, nous utiliserons la classe de test NUnit [NunitTestSpringIocDemo3] suivante :

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
        ' l'objet à tester
        Private factory As XmlObjectFactory

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

        <TearDown()> _
        Public Sub destroy()
            ' on détruit les singletons
            factory.Dispose()
            ' suivi
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
    Public Sub demo3()
            ' récupération du singleton [groupe1]
            Dim groupe1 As GroupePersonnes = CType(factory.GetObject("groupe1"), GroupePersonnes)
            Console.WriteLine("groupe1={0}", groupe1)
        End Sub

    End Class
End Namespace

La méthode [demo3] récupère le singleton [groupe1] et l'affiche.

La structure du projet Visual Studio reste ce qu'elle était auparavant si ce n'est qu'il a deux classes ainsi qu'un fichier de configuration supplémentaires.

Image

L'exécution de la méthode [demo3] du test NUnit donne les résultats suivants :

Image

Commentaires :

***** 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
  • ligne 2 : méthode d'attribut [Setup] exécutée avant chaque test, ici [demo]
  • ligne 3-4 : Spring commence la création du singleton [groupe1]. Celui-ci a une dépendance vis à vis des singletons [personne1, personne2] qui n'existent pas. Ces derniers sont créés et leurs méthodes [init] exécutées.
  • ligne 5 : le singleton [groupe1] peut maintenant être créé. Sa méthode [init] est alors exécutée.
  • ligne 6 : la méthode [demo3] fait afficher la valeur du singleton [groupe1]
  • lignes 7-10 : la méthode [TearDown] du test est exécutée. Les singletons sont détruits d'où l'exécution de leurs méthodes [destroy]