2. Configurer une application avec Spring
Considérons une application 3-tier classique :
![]() |
On supposera que l'accès la couche DAO est contrôlé par une interface [IArticlesDao] :
....
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' liste de tous les articles
Function getAllArticles() As IList
' ajoute un article
Function ajouteArticle(ByVal unArticle As Article) As Integer
' supprime un article
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modifie un article
Function modifieArticle(ByVal unArticle As Article) As Integer
' recherche un article
Function getArticleById(ByVal idArticle As Integer) As Article
' supprime tous les articles
Sub clearAllArticles()
' insère des articles au sein d'une transaction
Sub doInsertionsInTransaction(ByVal articles As Article())
' change le stock d'u article
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
Dans la couche d'accès aux données ou couche DAO (Data Access Object), il est fréquent de travailler avec un SGBD. Considérons le cas où celui-ci est accédé via un pilote ODBC. Le squelette d'une classe accédant à cette source ODBC pourrait être le suivant :
NameSpace istia.st.articles.dao
Imports System.Data.Odbc
...
Public Class ArticlesDaoPlainODBC
Implements istia.st.articles.dao.IArticlesDao
' champs privés
Private connexion As OdbcConnection = Nothing
Private DSN As String
Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)
'on récupère le nom de la source ODBC
Me.DSN = DSN
' on crée la chaîne de connexion
Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
'on instancie la connexion
connexion = New OdbcConnection(connectString)
End Sub
....
End Class
End NameSpace
Pour faire une opération sur la source ODBC, toute méthode a besoin d'un objet [OdbcConnection] qui représente la connexion à la base par laquelle vont transiter les échanges entre celle-ci et l'application. Pour construire cet objet, on a besoin de trois informations :
le nom de la source ODBC | |
l'identité sous laquelle on crée la connexion | |
le mot de passe associée à cette identité |
Notre classe [ArticlesDaoPlainODBC] obtient ces informations via l'agent extérieur qui instancie un membre de la classe. On peut se demander comment celui-ci obtient les trois informations nécessaires à l'instanciation de la classe [ArticlesDaoPlainODBC]. Prenons un exemple. Supposons qu'on veuille écrire une classe de test de la couche [Dao]. On aurait l'architecture suivante :
![]() |
Le squelette d'une classe de test Nunit [http://www.nunit.org/] pourrait être le suivant :
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
<TestFixture()> _
Public Class NunitTestArticlesDaoPlainOdbc
' l'objet à tester
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' on crée une instance de l'objet à tester
articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
End Sub
<Test()> _
Public Sub testGetAllArticles()
' vérification visuelle
listArticles()
End Sub
' listing écran
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
L'environnement de test Nunit est un portage vers la plate-forme .NET de l'environnement JUnit qui existe pour la plate-forme Java. Dans la classe ci-dessus, la méthode ayant l'attribut <SetUp()> est exécutée avant chaque méthode de test. Celle qui a l'attribut <TearDown()> est elle, exécutée après chaque test. Il n'y en a pas dans l'exemple ci-dessus. Ici, on voit que la méthode [init] qui a l'attribut <SetUp()>, instancie un objet [ArticlesDaoPlainODBC] en lui passant en "dur" les trois informations dont le constructeur de cet objet a besoin.
Notre classe de test est à la merci d'un changement d'une des informations codées en "dur". Il serait préférable que celles-ci soient inscrites dans un fichier de configuration, afin d'éviter des recompilations inutiles lorsqu'elles changent. La solution habituelle pour configurer une application est l'utilisation d'un fichier où l'on va retrouver toutes les informations susceptibles de changer dans le temps. Il existe une grande variété de fichiers de configuration. La tendance actuelle est d'utiliser des fichiers XML. C'est l'option prise par Spring. Le fichier configurant un objet [ArticlesDaoPlainODBC] pourrait être 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>
<!-- la classe d'implémentation de l'interface IArticlesDao -->
<description>Gestion d'une table d'articles</description>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
<constructor-arg index="0">
<value>odbc-firebird-articles</value>
</constructor-arg>
<constructor-arg index="1">
<value>SYSDBA</value>
</constructor-arg>
<constructor-arg index="2">
<value>masterkey</value>
</constructor-arg>
</object>
</objects>
Le fichier de configuration de Spring décrit des objets à instancier. Il ne dit pas, en général, à quel moment ils seront instanciés. Le moment de leur instanciation est alors décidé par le code qui exploite ce fichier. Les objets décrits dans un fichier de configuration Spring peuvent être instanciés et initialisés de deux façons différentes :
- en indiquant, comme ci-dessus, les paramètres à passer au constructeur de l'objet
- en fournissant des valeurs aux propriétés de l'objet (Property). Dans ce cas, l'objet doit posséder un constructeur par défaut que Spring utilisera pour l'instanciation.
Les objets qui, dans une application, ont pour rôle de rendre un service sont souvent créés en un seul exemplaire. On les appelle des singletons. Ainsi dans notre exemple d'application multi-tier présentée au début de ce document, l'accès à la base des articles sera assuré par un unique exemplaire de la classe [ArticlesDaoPlainODBC]. Pour une application web, ces objets de service servent plusieurs clients à la fois. On ne crée pas un objet de service par client.
Le fichier de configuration Spring ci-dessus permet de créer un objet service unique de type [ArticlesDaoPlainODBC] dans un paquetage nommé [istia.st.articles.dao]. Les trois informations nécessaires au constructeur de cet objet sont définies à l'intérieur d'une balise <object>...</object>. On aura autant de telles balises <object> que de singletons à construire.
Détaillons la configuration :
<objects> est la balise racine d'un fichier de configuration Spring. Elle annonce la description des objets singleton à instancier.
La balise <description> est facultative. On peut l'utiliser par exemple pour décrire le rôle du fichier de configuration.
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
...
</object>
La balise <object> sert à décrire un objet à instancier. Elle a ici deux attributs :
- name : identifiant de l'objet. C'est via ce nom, que le code extérieur référencera l'objet.
- class : de la forme "nom de classe, nom d'assemblage". La première information est le nom complet de la classe à instancier. La seconde, le nom de la DLL qui contient cette classe. Dans notre exemple, la classe se trouve dans un fichier nommé [articlesdao.dll]
Le contenu de la balise <object> sert à décrire le mode d'instanciation de l'objet :
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
<constructor-arg index="0">
<value>odbc-firebird-articles</value>
</constructor-arg>
<constructor-arg index="1">
<value>SYSDBA</value>
</constructor-arg>
<constructor-arg index="2">
<value>masterkey</value>
</constructor-arg>
</object>
Rappelons la signature du constructeur de la classe [ArticlesDaoPlainODBC] :
L'objet Spring [articlesdao] sera instancié par le constructeur ci-dessus avec les trois informations du fichier de configuration : [odbc-firebird-articles, SYSDBA, masterkey].
A quel moment va intervenir la construction des objets définis dans le fichier Spring ? On trouve dans toute application, une méthode assurée d'être la première à s'exécuter. C'est généralement dans celle-ci que la construction des singletons est demandée. L'initialisation d'une application peut être dévolue à la méthode main de cette même application si elle en a une. Pour une application ASP.NET, ce peut-être la méthode [Application_Start] du fichier [global.asax]. Pour notre classe de test [Nunit], l'initialisation de l'application a lieu dans la méthode associée à l'attribut <Setup()>.
Comment utiliser le fichier de configuration ci-dessus dans notre classe [Nunit] ? Voici un exemple :
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
<TestFixture()> _
Public Class NunitSpringTestArticlesDaoPlainOdbc
' l'objet à tester
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' on récupère une instance du fabricant d'objets Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
' on demande l'instanciation de l'objet articles dao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' vérification visuelle
listArticles()
End Sub
' listing écran
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
Commentaires :
- pour instancier les objets du fichier de configuration de Spring, on passe par un objet de type [XmlObjectFactory]. C'est un objet de type "Factory", c.a.d. un objet qui sert à créer d'autres objets (Factory=usine, fabrique). Spring dispose de plusieurs types de "Factory" selon le fichier de configuration utilisé. Ici, celui-ci est un fichier XML et donc on utilise un type [XmlObjectFactory].
- de façon assez logique, un objet de type [XmlObjectFactory] a besoin du nom du fichier XML de configuration, ici [spring-config-plainodbc.xml]. Très exactement, le type [XmlObjectFactory] s'instancie avec un flux de lecture créé à partir du fichier XML dont on donne le nom.
- une fois l'objet de type [XmlObjectFactory] créé, un objet du fichier de configuration est obtenu par [XmlObjectFactory].getObject("identifiant") où "identifiant" est l'attribut [id] d'un des objets du fichier de configuration.
- si l'objet demandé n'a pas déjà été instancié, Spring l'instancie à l'aide des informations de son fichier de configuration et en rend une référence au programme appelant. Si l'objet a déjà été instancié, Spring se contente de rendre la référence de l'objet déjà existant. C'est le principe du singleton.
- on remarquera que la classe de test [Nunit] ne connaît pas le nom de la classe d'accès aux données. Ce nom est dans le fichier de configuration. La classe de test se contente de demander un objet implémentant l'interface [IArticlesDao] :
' l'objet à tester
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
...
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
C'est là tout l'intérêt de Spring. Si on change de classe d'implémentation, notre classe de test n'aura pas à être changée. On modifiera simplement le fichier de configuration de Spring. La classe de test elle, se contente de travailler avec une interface et non avec une classe.
Terminons cette présentation par quelques points pratiques.
Quand on écrit "Spring va instancier...", que veut-on dire exactement ? Pour la plate-forme .NET, Spring est contenu dans trois fichiers :

Pour un projet .NET construit avec Visual Studio et qui doit utiliser Spring, on procèdera comme suit :
- les trois fichiers ci-dessus seront placés dans le dossier [bin] du projet
- [Spring.Core.dll] doit faire partie des références du projet :

- que les classes faisant appel à Spring, devront importer certains espaces de noms dont souvent le suivant :
Autre point pratique : où place-t-on le fichier de configuration de Spring ? Il y a plusieurs endroits possibles. L'un d'eux est le dossier [bin] du projet. C'est là qu'a été placé le fichier [spring-config-plainodbc.xml] de l'exemple étudié.

