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 :

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

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

L'exécution de la méthode [demo] du test NUnit donne les résultats indiqués ci-dessus et repris ci-dessous :
Commentaires :
- ligne 2 : le test démarre avec l'excécution de la méthode d'attribut <Setup()>
- ligne 3 : l'opération
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é.
- ligne 4 : l'opération
a fait afficher la valeur de l'objet [Personne] créé.
- lignes 5-6 : le même phénomène se répète pour le bean de clé [personne2].
- 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 :
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
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 :
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 :

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

Commentons les affichages écran :
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>
- la balise <list> permet d'initialiser avec différentes valeurs un champ de type tableau ou de type IList.
- 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.

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

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