Skip to content

2. Een applicatie configureren met Spring

Laten we eens kijken naar een klassieke 3-tier-applicatie:

We gaan ervan uit dat de toegang tot de DAO-laag wordt geregeld door een interface [IArticlesDao]:

....
Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' lijst van alle artikelen
        Function getAllArticles() As IList
         ' voegt een artikel toe
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' verwijdert een artikel
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' wijzigt een artikel
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' een artikel zoeken
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' verwijdert alle artikelen
        Sub clearAllArticles()
         ' voegt artikelen toe aan een transactie
        Sub doInsertionsInTransaction(ByVal articles As Article())
         ' wijzigt de voorraad van een artikel
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

In de gegevenslaag of DAO-laag (Data Access Object) wordt vaak gewerkt met een SGBD. Laten we eens kijken naar het geval waarin deze wordt benaderd via een stuurprogramma ODBC. Het raamwerk van een klasse die toegang biedt tot deze bron ODBC zou er als volgt uit kunnen zien:

NameSpace istia.st.articles.dao

Imports System.Data.Odbc
...
    Public Class ArticlesDaoPlainODBC
        Implements istia.st.articles.dao.IArticlesDao

         ' privévelden
        Private connexion As OdbcConnection = Nothing
        Private DSN As String

        Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)
             'haalt de naam van de bron op ODBC
            Me.DSN = DSN
             ' de verbindingsstring wordt aangemaakt
            Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
             'de verbinding wordt geïnitialiseerd
            connexion = New OdbcConnection(connectString)
        End Sub

....
    End Class
End NameSpace

Om een bewerking uit te voeren op de bron ODBC, heeft elke methode een object [OdbcConnection] nodig dat de verbinding met de database vertegenwoordigt, via welke de gegevensuitwisseling tussen de database en de applicatie verloopt. Om dit object aan te maken, zijn drie gegevens nodig:

DSN As String
de naam van de bron ODBC
user As String
de identiteit waaronder de verbinding wordt aangemaakt
passwd As String
het wachtwoord dat bij deze identiteit hoort

Onze klasse [ArticlesDaoPlainODBC] verkrijgt deze informatie via de externe agent die een lid van de klasse instantiëert. Men kan zich afvragen hoe deze agent de drie gegevens verkrijgt die nodig zijn voor het instantiëren van de klasse [ArticlesDaoPlainODBC]. Laten we een voorbeeld nemen. Stel dat we een testklasse willen schrijven voor de laag [Dao]. Dan zouden we de volgende architectuur hebben:

Het raamwerk van een Nunit-testklasse [http://www.nunit.org/] zou er als volgt uit kunnen zien:

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

         ' het te testen object
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' we maken een instantie van het te testen object
            articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' visuele controle
            listArticles()
        End Sub

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

De Nunit-testomgeving is een port naar het .NET-platform van de JUnit-omgeving die voor het Java-platform bestaat. In de bovenstaande klasse wordt de methode met het attribuut <SetUp()> vóór elke testmethode uitgevoerd. De methode met het attribuut <TearDown()> wordt daarentegen na elke test uitgevoerd. In het bovenstaande voorbeeld komt deze niet voor. Hier zien we dat de methode [init], die het attribuut <SetUp()> heeft, een object [ArticlesDaoPlainODBC] instantiëert door de drie gegevens die de constructor van dit object nodig heeft, ‘hard’ door te geven.

Onze testklasse is overgeleverd aan een wijziging van een van de ‘hard gecodeerde’ gegevens. Het zou beter zijn als deze in een configuratiebestand zouden worden vastgelegd, om onnodige hercompilaties te voorkomen wanneer ze veranderen. De gebruikelijke oplossing voor het configureren van een applicatie is het gebruik van een bestand waarin alle gegevens staan die in de loop van de tijd kunnen veranderen. Er bestaat een grote verscheidenheid aan configuratiebestanden. De huidige trend is om XML-bestanden te gebruiken. Dit is de keuze die Spring heeft gemaakt. Het configuratiebestand voor een [ArticlesDaoPlainODBC]-object zou er als volgt uit kunnen zien:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
     <!-- de implementatieklasse van de 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>

Het Spring-configuratiebestand beschrijft objecten die moeten worden geïnstantieerd. Het geeft over het algemeen niet aan wanneer ze worden geïnstantieerd. Het moment van hun instantiatie wordt dan bepaald door de code die dit bestand gebruikt. De objecten die in een Spring-configuratiebestand worden beschreven, kunnen op twee verschillende manieren worden geïnstantieerd en geïnitialiseerd:

  • door, zoals hierboven, de parameters op te geven die aan de constructor van het object moeten worden doorgegeven
  • door waarden op te geven voor de eigenschappen (Property) van het object. In dit geval moet het object een standaardconstructor hebben die Spring zal gebruiken voor het instantiëren.

Objecten die in een applicatie tot taak hebben een dienst te verlenen, worden vaak in één enkel exemplaar aangemaakt. Deze worden singletons genoemd. Zo zal in ons voorbeeld van een meerlaagse applicatie, dat aan het begin van dit document werd gepresenteerd, de toegang tot de artikeldatabase worden verzorgd door één enkel exemplaar van de klasse [ArticlesDaoPlainODBC]. In een webapplicatie bedienen deze serviceobjecten meerdere clients tegelijk. Er wordt niet per client een serviceobject aangemaakt.

Met het bovenstaande Spring-configuratiebestand kan één enkel serviceobject van het type [ArticlesDaoPlainODBC] worden aangemaakt in een pakket met de naam [istia.st.articles.dao]. De drie gegevens die nodig zijn voor de constructor van dit object worden gedefinieerd binnen een tag <object>...</object>. Er zijn evenveel van dergelijke <object>-tags als er singletons moeten worden aangemaakt.

Laten we de configuratie nader bekijken:

<objects>
...
</objects>

<objects> is de hoofdtag van een Spring-configuratiebestand. Deze tag geeft aan dat er singleton-objecten moeten worden geïnstantieerd.

    <description>Gestion d'une table d'articles</description>

De tag <description> is optioneel. Deze kan bijvoorbeeld worden gebruikt om de functie van het configuratiebestand te beschrijven.

<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
...    
</object>

De tag <object> wordt gebruikt om een object te beschrijven dat moet worden geïnstantieerd. Deze tag heeft hier twee attributen:

  • name: de identificatie van het object. Via deze naam zal de externe code naar het object verwijzen.
  • class: in de vorm "klassenaam, assemblynaam". Het eerste onderdeel is de volledige naam van de klasse die moet worden geïnstantieerd. Het tweede onderdeel is de naam van de DLL die deze klasse bevat. In ons voorbeeld bevindt de klasse zich in een bestand met de naam [articlesdao.dll]

De inhoud van de tag <object> beschrijft hoe het object moet worden geïnstantieerd:

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

Ter herinnering: de handtekening van de fabrikant van de klasse [ArticlesDaoPlainODBC]:

        Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)

Het Spring-object [articlesdao] wordt door de bovenstaande constructor geïnstantieerd met de drie gegevens uit het configuratiebestand: [odbc-firebird-articles, SYSDBA, masterkey].

Op welk moment vindt de aanmaak van de in het Spring-bestand gedefinieerde objecten plaats? In elke applicatie is er een methode waarvan zeker is dat deze als eerste wordt uitgevoerd. Meestal wordt in deze methode de aanmaak van de singletons aangevraagd. Het initialiseren van een applicatie kan worden toevertrouwd aan de methode main van diezelfde applicatie, indien deze er een heeft. Voor een applicatie met de naam ASP.NET kan dit de methode [Application_Start] uit het bestand [global.asax] zijn. Voor onze testklasse [Nunit] vindt de initialisatie van de applicatie plaats in de methode die is gekoppeld aan het attribuut <Setup()>.

Hoe gebruiken we het bovenstaande configuratiebestand in onze klasse [Nunit]? Hier volgt een voorbeeld:

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

         ' het te testen object
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
       ' we halen een instantie op van de Spring-objectfabrikant
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
       ' we vragen om het instantiëren van het DAO-object ‘artikelen’
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' visuele controle
            listArticles()
        End Sub

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

Opmerkingen:

  • om objecten uit het Spring-configuratiebestand te instantiëren, maken we gebruik van een object van het type [XmlObjectFactory]. Dit is een object van het type "Factory", c.a.d. Een object dat dient om andere objecten aan te maken (Factory = fabriek). Spring beschikt over verschillende soorten "Factory", afhankelijk van het gebruikte configuratiebestand. In dit geval is dat een XML-bestand en daarom gebruiken we een type [XmlObjectFactory].
  • Logischerwijs heeft een object van het type [XmlObjectFactory] de naam nodig van het configuratiebestand XML, in dit geval [spring-config-plainodbc.xml]. Om precies te zijn: het type [XmlObjectFactory] wordt geïnstantieerd met een leesstroom die is aangemaakt op basis van het configuratiebestand XML, waarvan de naam wordt opgegeven.
  • Zodra het object van het type [XmlObjectFactory] is aangemaakt, wordt via [XmlObjectFactory] een object uit het configuratiebestand opgehaald.getObject("identificatie") waarbij "identificatie" het attribuut [id] is van een van de objecten in het configuratiebestand.
  • Als het gevraagde object nog niet is geïnstantieerd, instantiëert Spring het aan de hand van de informatie in het configuratiebestand en geeft het een verwijzing naar het object terug aan het aanroepende programma. Als het object al is geïnstantieerd, geeft Spring gewoon de verwijzing naar het reeds bestaande object terug. Dit is het principe van de singleton.
  • Merk op dat de testklasse [Nunit] de naam van de klasse voor gegevenstoegang niet kent. Deze naam staat in het configuratiebestand. De testklasse vraagt alleen om een object dat de interface [IArticlesDao] implementeert:
         ' het te testen object
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
...
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

Dat is precies het voordeel van Spring. Als we van implementatieklasse veranderen, hoeft onze testklasse niet te worden aangepast. We hoeven alleen het configuratiebestand van Spring aan te passen. De testklasse werkt namelijk met een interface en niet met een klasse.

Laten we deze presentatie afsluiten met enkele praktische punten.

Wat bedoelen we precies als we schrijven: „Spring zal een instantie maken van...“? Voor het .NET-platform is Spring opgenomen in drie bestanden:

Image

Voor een .NET-project dat met Visual Studio is gebouwd en Spring moet gebruiken, gaan we als volgt te werk:

  • de drie bovengenoemde bestanden worden in de map [bin] van het project geplaatst
  • [Spring.Core.dll] moet deel uitmaken van de verwijzingen van het project:

Image

  • De klassen die gebruikmaken van Spring moeten bepaalde naamruimten importeren, waaronder vaak de volgende:
Imports Spring.Objects.Factory.Xml

Nog een praktisch punt: waar plaatsen we het Spring-configuratiebestand? Er zijn verschillende mogelijke locaties. Een daarvan is de map [bin] van het project. Daar is het bestand [spring-config-plainodbc.xml] uit het bestudeerde voorbeeld geplaatst.