7. Une solution
7.1. Le projet Visual Studio
![]() | ![]() |
Commentaires :
- les fichiers nécessaires à Spring ont été placés dans le dossier [bin] : Spring.Core.dll, log4net.dll, Spring.Core.xml
- les fichiers de configuration de Spring [spring-config-3tier-*.xml] ont eux aussi été placés dans le dossier [bin]
- sous la racine [istia], on trouve les différentes classes de l'application
Le projet a été configuré pour générer la DLL [spring3tier.dll] dans le dossier [bin] :

7.2. Le paquetage [istia.st.spring3tier.dao]
L'interface IDao :
Namespace istia.st.spring3tier.dao
Public Interface IDao
' faire qq chose dans la couche [dao]
Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
End Interface
End Namespace
Une première classe d'implémentation :
Namespace istia.st.spring3tier.dao
Public Class DaoImpl1
Implements istia.st.spring3tier.dao.IDao
' faire qq chose dans la couche [dao]
Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer
Return a + b
End Function
End Class
End Namespace
- la classe n'a pas de champ privé
- La méthode [doSomethingInDaoLayer] rend la somme de ses paramètres comme demandé.
Une seconde classe d'implémentation :
Namespace istia.st.spring3tier.dao
Public Class DaoImpl2
Implements istia.st.spring3tier.dao.IDao
' faire qq chose dans la couche [dao]
Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer
Return a - b
End Function
End Class
End Namespace
- la classe n'a pas de champ privé
- la méthode [doSomethingInDaoLayer] rend la différence de ses paramètres comme demandé.
7.3. Le paquetage [istia.st.spring3tier.domain]
L'interface IDomain :
Namespace istia.st.spring3tier.domain
Public Interface IDomain
' faire qq chose dans la couche [domain]
Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
End Interface
End Namespace
Une première classe d'implémentation IDomainImpl1 :
Imports istia.st.spring3tier.dao
Namespace istia.st.spring3tier.domain
Public Class DomainImpl1
Implements istia.st.spring3tier.domain.IDomain
' champs privés
Private _dao As IDao
' propriété associée
Public WriteOnly Property dao() As IDao
Set(ByVal Value As IDao)
_dao = Value
End Set
End Property
' constructeur par défaut
Public Sub New()
End Sub
' faire qq chose dans la couche [domain]
Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
a += 1
b += 1
Return _dao.doSomethingInDaoLayer(a, b)
End Function
End Class
End Namespace
- la classe a un champ privé qui est une référence au singleton de type [IDao] qui donne accès à la couche [Dao]. Ce champ sera initialisé par Spring (injection de dépendance) au moment de la construction de l'objet.
- La méthode [doSomethingInDomainLayer] incrémente ses paramètres puis les passe à la méthode [doSomethingInDaoLayer] du singleton [dao]
Une seconde classe d'implémentation IDomainImpl2 :
Imports istia.st.spring3tier.dao
Namespace istia.st.spring3tier.domain
Public Class DomainImpl2
Implements istia.st.spring3tier.domain.IDomain
' champs privés
Private _dao As IDao
' propriété associée
Public WriteOnly Property dao() As IDao
Set(ByVal Value As IDao)
_dao = Value
End Set
End Property
' constructeur par défaut
Public Sub New()
End Sub
' faire qq chose dans la couche [domain]
Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
a -= 1
b -= 1
Return _dao.doSomethingInDaoLayer(a, b)
End Function
End Class
End Namespace
- la classe a un champ privé qui est une référence au singleton de type [IDao] qui donne accès à la couche [Dao]. Ce champ sera initialisé par Spring (injection de dépendance) au moment de la construction de l'objet.
- La méthode [doSomethingInDomainLayer] décrémente ses paramètres puis les passe à la méthode [doSomethingInDaoLayer] du singleton [dao]
7.4. Le paquetage [istia.st.spring3tier.control]
L'interface IControl :
Namespace istia.st.spring3tier.control
Public Interface IControl
' faire qq chose dans la couche [control]
Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
End Interface
End Namespace
Une première classe d'implémentation ControlImpl1 :
Imports istia.st.spring3tier.domain
Namespace istia.st.spring3tier.control
Public Class ControlImpl1
Implements istia.st.spring3tier.control.IControl
' champs privés
Private _domain As IDomain
' propriété associée
Public WriteOnly Property domain() As IDomain
Set(ByVal Value As IDomain)
_domain = Value
End Set
End Property
' constructeur par défaut
Public Sub New()
End Sub
' faire qq chose dans la couche [control]
Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
a += 1
b += 1
Return _domain.doSomethingInDomainLayer(a, b)
End Function
End Class
End Namespace
- la classe a un champ privé qui est une référence au singleton de type [IDomain] qui donne accès à la couche [Domain]. Ce champ sera initialisé par Spring (injection de dépendance) au moment de la construction de l'objet.
- La méthode [doSomethingInControlLayer] incrémente ses paramètres puis les passe à la méthode [doSomethingInDomainLayer] du singleton [domain]
Une seconde classe d'implémentation IControlImpl2 :
Imports istia.st.spring3tier.domain
Namespace istia.st.spring3tier.control
Public Class ControlImpl2
Implements istia.st.spring3tier.control.IControl
' champs privés
Private _domain As IDomain
' propriété associée
Public WriteOnly Property domain() As IDomain
Set(ByVal Value As IDomain)
_domain = Value
End Set
End Property
' constructeur par défaut
Public Sub New()
End Sub
' faire qq chose dans la couche [control]
Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
a -= 1
b -= 1
Return _domain.doSomethingInDomainLayer(a, b)
End Function
End Class
End Namespace
- la classe a un champ privé qui est une référence au singleton de type [IDomain] qui donne accès à la couche [Domain]. Ce champ sera initialisé par Spring (injection de dépendance) au moment de la construction de l'objet.
- La méthode [doSomethingInControlLayer] décrémente ses paramètres puis les passe à la méthode [doSomethingInDomainLayer] du singleton [domain].
7.5. Les fichiers de configuration [Spring]
Le fichier [spring-config-3tier-1.xml] utilise les versions 1 des implémentations :
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<!-- la classe dao -->
<object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
<!-- la classe domain -->
<object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
<property name="dao">
<ref object="dao" />
</property>
</object>
<!-- la classe control -->
<object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
<property name="domain">
<ref object="domain" />
</property>
</object>
</objects>
Le fichier [spring-config-3tier-2.xml] utilise les versions 2 des implémentations :
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<!-- la classe dao -->
<object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
<!-- la classe domain -->
<object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
<property name="dao">
<ref object="dao" />
</property>
</object>
<!-- la classe control -->
<object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
<property name="domain">
<ref object="domain" />
</property>
</object>
</objects>
7.6. Le paquetage des tests [istia.st.spring3tier.tests]
Un test Nunit [NunitTestSpring3tier.vb] :
Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain
Namespace istia.st.springioc.tests
<TestFixture()> _
Public Class NunitTestSpring3tier
' les fabriques de singleton
Private factory1 As XmlObjectFactory
Private factory2 As XmlObjectFactory
<SetUp()> _
Public Sub init()
' on crée les fabriques de singleton
factory1 = New XmlObjectFactory(New FileStream("spring-config-3tier-1.xml", FileMode.Open))
factory2 = New XmlObjectFactory(New FileStream("spring-config-3tier-2.xml", FileMode.Open))
End Sub
<TearDown()> _
Public Sub destroy()
' on détruit les singletons
factory1.Dispose()
factory2.Dispose()
' on libère les fabriques de singletons
factory1 = Nothing
factory2 = Nothing
End Sub
<Test()> _
Public Sub test()
'on récupère une implémentation de l'interface IControl
Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
' on utilise la classe
Dim a1 As Integer = 10, b1 As Integer = 20
Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
Assert.AreEqual(34, res1)
' on récupère une autre implémentation de l'interface IControl
Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
' on utilise la classe
Dim a2 As Integer = 10, b2 As Integer = 20
Dim res2 As Integer = control2.doSomethingInControlLayer(a2, b2)
Assert.AreEqual(-10, res2)
End Sub
End Class
End Namespace
L'exécution de ce test donne les résultats suivants :

Le lecteur qui a une version avec couleurs de ce document verra que les résultats sont au "vert" indiquant par là la réussite du test.
7.7. Un autre type de fichier de configuration de Spring
Une application VB.net peut être configurée à l'aide d'un fichier appelé [App.config]. Ce fichier est placé à la racine du projet Visual Studio :

Spring peut tirer parti de ce fichier de configuration. Considérons le fichier [App.config] suivant, inspiré d'exemples de la documentation de [Spring.net] :
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
<resource uri="config://spring/objects" />
</context>
<objects>
<!-- une première configuration -->
<!-- la classe dao -->
<object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
<!-- la classe domain -->
<object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
<property name="dao">
<ref object="dao1" />
</property>
</object>
<!-- la classe control -->
<object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
<property name="domain">
<ref object="domain1" />
</property>
</object>
<!-- une deuxième configuration -->
<!-- la classe dao -->
<object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
<!-- la classe domain -->
<object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
<property name="dao">
<ref object="dao2" />
</property>
</object>
<!-- la classe control -->
<object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
<property name="domain">
<ref object="domain2" />
</property>
</object>
</objects>
</spring>
</configuration>
Note : les informations ci-dessous sont données avec réserve. Je ne suis pas sûr d'avoir correctement compris la signification de tous les éléments du fichier de configuration ci-dessus.
La syntaxe XML du fichier [App.config] exige qu'il respecte la syntaxe :
La gestion des différentes sections de [App.config] peut être déléguée à des programmes externes. C'est ce qui est fait ici dans la section [ConfigSections] :
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
Le code ci-dessus signifie que la section appelée [spring/context] doit être gérée par la classe [Spring.Context.Support.ContextHandler] qu'on trouvera dans l'assemblage [Spring.Core.dll] et que la section appelée [spring/objects] doit être gérée par la classe [Spring.Context.Support.DefaultSectionHandler] qu'on trouvera là encore dans l'assemblage [Spring.Core.dll].
La section [spring/context] est la suivante :
<spring>
<context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
<resource uri="config://spring/objects" />
</context>
...
</spring>
On y semble dire que la section [spring/objects] du fichier de configuration doit être gérée par la classe [Spring.Context.Support.XmlApplicationHandler] qu'on trouvera dans l'assemblage [Spring.Core.dll]. Cette classe doit exploiter une ressource XML dont l'emplacement est [ config://spring/objects], c.a.d. la section [spring/objects] du fichier de configuration courant.
On retrouve dans la section [spring/objects] la syntaxe Spring à laquelle nous sommes maintenant habitués.
Nous avons dans la section <objects>... </objects> du fichier [App.config] défini deux configurations possibles pour notre application 3tier :
- une qui utilise la version 1 des implémentations d'interfaces
- une autre qui utilise la version 2 de ces mêmes implémentations
Maintenant comment exploiter le fichier [App.config] ?
Le code suivant montre une application console qui exploite le fichier [App.config] précédent :
Imports System
Imports Spring.Context
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain
Imports System.Configuration
Namespace istia.st.springioc.tests
Module MainTestSpring3tier
Public Sub main()
' le contexte Spring qui va nous permettre de récupérer les singletons
Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
' on récupère une 1ère implémentation de l'interface IControl
Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
' on utilise la classe
Dim a1 As Integer = 10, b1 As Integer = 20
Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
' on récupère une autre implémentation de l'interface IControl
Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
' on utilise la classe
Dim a2 As Integer = 10, b2 As Integer = 20
Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
' pause
Console.WriteLine("Tapez [entrée] pour continuer...")
Console.ReadLine()
End Sub
End Module
End Namespace
Commentaires :
- dans les exemples NUnit que nous avons utilisés jusqu'ici, nous utilisions un objet [XmlObjectFactory] pour obtenir les singletons dont nous avions besoin. Ici l'objet utilisé est de type [IApplicationContext], un interface de Spring. Il s'obtient à partir de [App.config] grâce à la classe [ConfigurationSettings], classe traditionnellement utilisée dans .NET pour exploiter les fichiers de configuration.
- on demande le gestionnaire de la section [spring/context]. Si on se reporte au fichier [App.config], on découvre que celui-ci est de type [XmlApplicationContext] et qu'il est chargé de gérer la section [spring/objects] de [App.config].
- une fois l'objet de type [IApplicationContext] récupéré, il s'utilise comme l'objet [XmlObjectFactory] que nous avions utilisé jusqu'à maintenant.
Le programme précédent s'appelle [MainTestSpring3tier.vb] et est placé dans le paquetage [tests] :

Le projet [spring3tier] est configuré pour que [MainTestSpring3tier] :

L'exécution du projet donne les résultats suivants :
7.8. Conclusion
Le framework Spring permet une réelle souplesse aussi bien dans l'architecture des applications que dans leur configuration. Nous avons utilisé le concept IoC, l'un des deux piliers de Spring. L'autre pilier est AOP (Aspect Oriented Programming) que nous n'avons pas présenté. Il permet d'ajouter, par configuration, du "comportement" à une méthode de classe sans modifier le code de celle-ci. Schématiquement, AOP permet de filtrer les appels à certaines méthodes :
![]() |
- le filtre peut être exécuté avant ou après la méthode M cible, ou les deux.
- la méthode M ignore l'existence de ces filtres. Ceux-ci sont définis dans le fichier de configuration de Spring.
- le code de la méthode M n'est pas modifié. Les filtres sont des classes Java à construire. Spring fournit des filtres prédéfinis, notamment pour gérer les transactions de SGBD.
- les filtres sont des beans et à ce titre sont définis dans le fichier de configuration de Spring en tant que beans.
Un filtre courant est le filtre transactionnel. Prenons une méthode M de la couche métier réalisant deux opérations indissociables sur des données (unité de travail). Elle fait appel à deux méthodes M1 et M2 de la couche DAO pour réaliser ces deux opérations.
![]() |
Parce qu'elle est dans la couche métier, la méthode M fait abstraction du support de ces données. Elle n'a pas, par exemple, à faire l'hypothèse que les données sont dans un SGBD et qu'elle a besoin de mettre les deux appels aux méthodes M1 et M2 au sein d'une transaction de SGBD. C'est à la couche DAO de s'occuper de ces détails. Une solution au problème précédent est alors de créer une méthode dans la couche DAO qui ferait elle-même appel aux méthodes M1 et M2, appels qu'elle engloberait dans une transaction de SGBD.
![]() |
La solution du filtrage AOP est plus souple. Elle va permettre de définir un filtre qui, avant l'appel de M va commencer une transaction et après l'appel va opérer un commit ou rollback selon les cas.
![]() |
Il y a plusieurs avantages à cette approche :
- une fois le filtre défini, il peut être appliqué à plusieurs méthodes, par exemple toutes celles qui ont besoin d'une transaction
- les méthodes ainsi filtrées n'ont pas à être réécrites
- les filtres à utiliser étant définis par configuration, on peut les changer
Pour davantage d'informations : http://www.springframework.net.





