Skip to content

7. Een oplossing

7.1. Het Visual Studio-project

Opmerkingen:

  • de bestanden die Spring nodig heeft, zijn in de map [bin] geplaatst: Spring.Core.dll, log4net.dll, Spring.Core.xml
  • De configuratiebestanden van Spring [spring-config-3tier-*.xml] zijn eveneens in de map [bin] geplaatst
  • onder de hoofdmap [istia] bevinden zich de verschillende klassen van de applicatie

Het project is geconfigureerd om de DLL [spring3tier.dll] te genereren in de map [bin]:

Image

7.2. Het pakket [istia.st.spring3tier.dao]

De interface IDao:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
         ' iets doen in de laag [dao]
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Een eerste voorbeeld van een implementatie:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl1
        Implements istia.st.spring3tier.dao.IDao

         ' iets doen in de laag [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
  • de klasse heeft geen privéveld
  • De methode [doSomethingInDaoLayer] retourneert de som van haar parameters zoals gevraagd.

Een tweede implementatieklasse:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl2
        Implements istia.st.spring3tier.dao.IDao

         ' iets doen in de laag [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
  • de klasse heeft geen privéveld
  • de methode [doSomethingInDaoLayer] geeft het verschil tussen de parameters weer, zoals gevraagd.

7.3. Het pakket [istia.st.spring3tier.domain]

De interface IDomain:

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
         ' iets doen in de laag [domain]
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Een eerste implementatieklasse IDomainImpl1:

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl1
        Implements istia.st.spring3tier.domain.IDomain

         ' privévelden
        Private _dao As IDao

         ' gekoppelde eigenschap
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' standaardconstructor
        Public Sub New()
        End Sub

         ' iets doen in de laag [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
  • De klasse heeft een privéveld dat verwijst naar het singleton van het type [IDao], dat toegang geeft tot de laag [Dao]. Dit veld wordt door Spring geïnitialiseerd (afhankelijkheidsinjectie) op het moment dat het object wordt aangemaakt.
  • De methode [doSomethingInDomainLayer] verhoogt de waarden van haar parameters en geeft deze vervolgens door aan de methode [doSomethingInDaoLayer] van het singleton [dao]

Een tweede implementatieklasse IDomainImpl2:

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl2
        Implements istia.st.spring3tier.domain.IDomain

         ' privévelden
        Private _dao As IDao

         ' gekoppelde eigenschap
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' standaardconstructor
        Public Sub New()
        End Sub

         ' iets doen in de laag [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
  • De klasse heeft een privéveld dat verwijst naar het singleton van het type [IDao], dat toegang geeft tot de laag [Dao]. Dit veld wordt door Spring geïnitialiseerd (afhankelijkheidsinjectie) op het moment dat het object wordt aangemaakt.
  • De methode [doSomethingInDomainLayer] verlaagt de waarden van haar parameters en geeft deze vervolgens door aan de methode [doSomethingInDaoLayer] van het singleton [dao]

7.4. Het pakket [istia.st.spring3tier.control]

De interface IControl:

Namespace istia.st.spring3tier.control
    Public Interface IControl
         ' iets doen in de laag [control]
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Een eerste implementatieklasse ControlImpl1:

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl1
        Implements istia.st.spring3tier.control.IControl

         ' privévelden
        Private _domain As IDomain

         ' gekoppelde eigenschap
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' standaardconstructor
        Public Sub New()
        End Sub

         ' iets doen in de laag [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
  • De klasse heeft een privéveld dat verwijst naar het singleton van het type [IDomain], dat toegang geeft tot de laag [Domain]. Dit veld wordt door Spring geïnitialiseerd (afhankelijkheidsinjectie) op het moment dat het object wordt aangemaakt.
  • De methode [doSomethingInControlLayer] verhoogt de waarden van haar parameters en geeft deze vervolgens door aan de methode [doSomethingInDomainLayer] van het singleton [domain]

Een tweede implementatieklasse IControlImpl2:

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl2
        Implements istia.st.spring3tier.control.IControl

         ' privévelden
        Private _domain As IDomain

         ' gekoppelde eigenschap
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' standaardconstructor
        Public Sub New()
        End Sub

         ' iets doen in de laag [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
  • De klasse heeft een privéveld dat verwijst naar het singleton van het type [IDomain], dat toegang geeft tot de laag [Domain]. Dit veld wordt door Spring geïnitialiseerd (afhankelijkheidsinjectie) op het moment dat het object wordt aangemaakt.
  • De methode [doSomethingInControlLayer] verlaagt de waarden van haar parameters en geeft deze vervolgens door aan de methode [doSomethingInDomainLayer] van het singleton [domain].

7.5. De configuratiebestanden [Spring]

Het bestand [spring-config-3tier-1.xml] maakt gebruik van versie 1 van de implementaties:

<?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 DAO-klasse -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
     <!-- de domain-klasse -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- de control-klasse -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

Het bestand [spring-config-3tier-2.xml] maakt gebruik van versie 2 van de implementaties:

<?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 DAO-klasse -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
     <!-- de klasse 'domain' -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- de klasse control -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

7.6. Het testpakket [istia.st.spring3tier.tests]

Een Nunit-test [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
         ' de singleton-fabrieken
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' we maken de singleton-fabrieken aan
            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()
             ' de singletons worden vernietigd
            factory1.Dispose()
            factory2.Dispose()
             ' de singleton-fabrieken vrijgeven
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
             ' een implementatie van de interface ophalen IControl
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
             ' de klasse wordt gebruikt
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
             ' we halen een andere implementatie van de interface op IControl
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
             ' we gebruiken de klasse
            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

Het uitvoeren van deze test levert de volgende resultaten op:

Image

De lezer die over een kleurversie van dit document beschikt, zal zien dat de resultaten "groen" zijn, wat aangeeft dat de test is geslaagd.

7.7. Een ander type Spring-configuratiebestand

Een toepassing VB.net kan worden geconfigureerd met behulp van een bestand met de naam [App.config]. Dit bestand wordt in de hoofdmap van het Visual Studio-project geplaatst:

Image

Spring kan gebruikmaken van dit configuratiebestand. Laten we eens kijken naar het volgende bestand [App.config], dat is geïnspireerd op voorbeelden uit de documentatie van [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>
             <!-- een eerste configuratie -->
             <!-- de DAO-klasse -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
             <!-- de klasse domain -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
             <!-- de klasse control -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
             <!-- een tweede configuratie -->
             <!-- de DAO-klasse -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
             <!-- de domain-klasse -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
             <!-- de control-klasse -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

Opmerking: de onderstaande informatie wordt onder voorbehoud verstrekt. Ik ben er niet zeker van of ik de betekenis van alle elementen in het bovenstaande configuratiebestand correct heb begrepen.


De syntaxis XML van het bestand [App.config] vereist dat het de volgende syntaxis volgt:

<configuration>
....
</configuration>

Het beheer van de verschillende secties van [App.config] kan worden gedelegeerd aan externe programma’s. Dit gebeurt hier in de sectie [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>

De bovenstaande code betekent dat de sectie met de naam [spring/context] moet worden beheerd door de klasse [Spring.Context.Support.ContextHandler], diete vinden is in de assemblage [Spring.Core.dll], en dat de sectie met de naam [spring/objects] moet worden beheerd door de klasse [Spring.Context.Support.DefaultSectionHandler], die eveneens te vinden is in de assemblage [Spring.Core.dll].

De sectie [spring/context] is als volgt:

    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
...
    </spring>

Hier lijkt te staan dat de sectie [spring/objects] van het configuratiebestand moet worden beheerd door de klasse [Spring.Context.Support.XmlApplicationHandler], die te vinden is in de assemblage [Spring.Core.dll]. Deze klasse moet gebruikmaken van een resource XML, waarvan de locatie [ config://spring/objects], c.a.d is, in de sectie [spring/objects] van het huidige configuratiebestand.

In de sectie [spring/objects] vinden we de Spring-syntaxis terug die we inmiddels gewend zijn.

In de sectie <objects>... </objects> van het bestand [App.config] hebben we twee mogelijke configuraties voor onze 3-tier-applicatie gedefinieerd:

  • een die versie 1 van de interface-implementaties gebruikt
  • een andere die versie 2 van diezelfde implementaties gebruikt

Hoe kunnen we nu het bestand [App.config] gebruiken?

De volgende code toont een console-applicatie die gebruikmaakt van het voorgaande bestand [App.config]:

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()
             ' de Spring-context waarmee we de singletons kunnen ophalen
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
             ' we halen een eerste implementatie van de interface op IControl
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
             ' we gebruiken de klasse
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
             ' we halen een andere implementatie van de interface op IControl
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
             ' we gebruiken de klasse
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
             ' pauze
            Console.WriteLine("Tapez [entrée] pour continuer...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

Opmerkingen:

  • in de voorbeelden met NUnit die we tot nu toe hebben gebruikt, maakten we gebruik van een object van het type [XmlObjectFactory] om de benodigde singletons te verkrijgen. Hier wordt een object van het type [IApplicationContext] gebruikt, een interface van Spring. Dit wordt verkregen vanuit [App.config] via de klasse [ConfigurationSettings], een klasse die traditioneel in .NET wordt gebruikt om configuratiebestanden te verwerken.
  • We roepen de handler van de sectie [spring/context] aan. Als we het bestand [App.config] raadplegen, zien we dat dit van het type [XmlApplicationContext] is en dat het verantwoordelijk is voor het beheer van de sectie [spring/objects] van [App.config].
  • Zodra het object van het type [IApplicationContext] is opgehaald, wordt het gebruikt zoals het object [XmlObjectFactory] dat we tot nu toe hadden gebruikt.

Het voorgaande programma heet [MainTestSpring3tier.vb] en bevindt zich in het pakket [tests]:

Image

Het project [spring3tier] is zo geconfigureerd dat [MainTestSpring3tier]:

Image

De uitvoering van het project levert de volgende resultaten op:

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

7.8. Conclusion

Het Spring-framework biedt echte flexibiliteit, zowel in de architectuur van de applicaties als in hun configuratie. We hebben gebruikgemaakt van het concept IoC, een van de twee pijlers van Spring. De andere pijler is AOP (Aspect Oriented Programming), die we niet hebben behandeld. Hiermee kan via configuratie „gedrag“ aan een klassemethode worden toegevoegd zonder de code ervan te wijzigen. Schematisch gezien maakt AOP het mogelijk om aanroepen van bepaalde methoden te filteren:

  • het filter kan vóór of na de doelmethode M worden uitgevoerd, of beide.
  • De methode M is zich niet bewust van het bestaan van deze filters. Deze worden gedefinieerd in het configuratiebestand van Spring.
  • De code van de M-methode wordt niet gewijzigd. Filters zijn Java-klassen die moeten worden geïmplementeerd. Spring biedt vooraf gedefinieerde filters, met name voor het beheer van transacties van SGBD.
  • Filters zijn beans en worden als zodanig in het Spring-configuratiebestand gedefinieerd als beans.

Een veelgebruikte filter is de transactiefilter. Laten we een methode M uit de bedrijfslaag nemen die twee onlosmakelijk met elkaar verbonden bewerkingen op gegevens uitvoert (werkeenheid). Deze methode roept twee methoden M1 en M2 uit de laag DAO aan om deze twee bewerkingen uit te voeren.

Omdat methode M zich in de bedrijfslaag bevindt, houdt deze geen rekening met het medium waarop deze gegevens zijn opgeslagen. De methode hoeft bijvoorbeeld niet ervan uitgaan dat de gegevens zich in een SGBD bevinden en dat de twee aanroepen van de methoden M1 en M2 binnen een transactie van SGBD moeten worden geplaatst. Het is aan de laag DAO om deze details te regelen. Een oplossing voor het voorgaande probleem is dan om in de laag DAO een methode aan te maken die zelf de methoden M1 en M2 aanroept, waarbij deze aanroepen worden opgenomen in een transactie van SGBD.

De filteroplossing AOP is flexibeler. Hiermee kan een filter worden gedefinieerd dat, vóór de aanroep van M, een transactie start en na de aanroep, afhankelijk van de situatie, een commit of rollback uitvoert.

Deze aanpak biedt verschillende voordelen:

  • zodra het filter is gedefinieerd, kan het op meerdere methoden worden toegepast, bijvoorbeeld op alle methoden die een transactie vereisen
  • de op deze manier gefilterde methoden hoeven niet te worden herschreven
  • aangezien de te gebruiken filters via de configuratie worden gedefinieerd, kunnen ze worden gewijzigd

Voor meer informatie: http://www.springframework.net.