Skip to content

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

Image

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 :

Image

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 :

Image

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 :

<configuration>
....
</configuration>

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

Image

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

Image

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

res1(10,20)=34
res2(10,20)=-10
Tapez [entrée] pour continuer...

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.