Skip to content

7. Eine Lösung

7.1. Das Visual Studio-Projekt

Anmerkungen:

  • Die für Spring erforderlichen Dateien wurden im Ordner [bin] abgelegt: Spring.Core.dll, log4net.dll, Spring.Core.xml
  • Die Spring-Konfigurationsdateien [spring-config-3tier-*.xml] wurden ebenfalls im Ordner [bin] abgelegt
  • Unter dem Stammverzeichnis [istia] befinden sich die verschiedenen Klassen der Anwendung

Das Projekt wurde so konfiguriert, dass die Dateien DLL und [spring3tier.dll] im Ordner [bin] generiert werden:

Image

7.2. Das Paket [istia.st.spring3tier.dao]

Die Schnittstelle IDao:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
         ' etwas in der Ebene tun [dao]
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Eine erste Implementierungsklasse:

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

         ' etwas in der Ebene tun [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
  • Die Klasse hat kein privates Feld
  • Die Methode [doSomethingInDaoLayer] gibt die Summe ihrer Parameter wie gewünscht zurück.

Eine zweite Implementierungsklasse:

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

         ' etwas in der Ebene tun [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
  • Die Klasse hat kein privates Feld
  • Die Methode [doSomethingInDaoLayer] gibt die Differenz ihrer Parameter wie gewünscht zurück.

7.3. Das Paket [istia.st.spring3tier.domain]

Die Schnittstelle IDomain:

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
         ' etwas in der Ebene [domain] tun
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Eine erste Implementierungsklasse IDomainImpl1:

Imports istia.st.spring3tier.dao

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

         ' private Felder
        Private _dao As IDao

         ' zugehörige Eigenschaft
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' Standardkonstruktor
        Public Sub New()
        End Sub

         ' etwas in der Ebene tun [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
  • Die Klasse verfügt über ein privates Feld, das eine Referenz auf das Singleton vom Typ [IDao] darstellt, welches Zugriff auf die Schicht [Dao] gewährt. Dieses Feld wird von Spring (Abhängigkeitsinjektion) beim Erstellen des Objekts initialisiert.
  • Die Methode [doSomethingInDomainLayer] inkrementiert ihre Parameter und übergibt sie anschließend an die Methode [doSomethingInDaoLayer] des Singletons [dao]

Eine zweite Implementierungsklasse IDomainImpl2:

Imports istia.st.spring3tier.dao

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

         ' private Felder
        Private _dao As IDao

         ' zugehörige Eigenschaft
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' Standardkonstruktor
        Public Sub New()
        End Sub

         ' etwas in der Schicht tun [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
  • Die Klasse verfügt über ein privates Feld, das eine Referenz auf das Singleton vom Typ [IDao] darstellt, welches Zugriff auf die Schicht [Dao] gewährt. Dieses Feld wird von Spring (Abhängigkeitsinjektion) beim Erstellen des Objekts initialisiert.
  • Die Methode [doSomethingInDomainLayer] dekrementiert ihre Parameter und übergibt sie anschließend an die Methode [doSomethingInDaoLayer] des Singletons [dao]

7.4. Das Paket [istia.st.spring3tier.control]

Die Schnittstelle IControl:

Namespace istia.st.spring3tier.control
    Public Interface IControl
         ' etwas in der Ebene tun [control]
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Eine erste Implementierungsklasse ControlImpl1:

Imports istia.st.spring3tier.domain

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

         ' private Felder
        Private _domain As IDomain

         ' zugehörige Eigenschaft
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' Standardkonstruktor
        Public Sub New()
        End Sub

         ' etwas in der Ebene tun [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
  • Die Klasse verfügt über ein privates Feld, das eine Referenz auf das Singleton vom Typ [IDomain] darstellt, welches Zugriff auf die Schicht [Domain] gewährt. Dieses Feld wird von Spring (Abhängigkeitsinjektion) beim Erstellen des Objekts initialisiert.
  • Die Methode [doSomethingInControlLayer] inkrementiert ihre Parameter und übergibt sie anschließend an die Methode [doSomethingInDomainLayer] des Singletons [domain]

Eine zweite Implementierungsklasse IControlImpl2:

Imports istia.st.spring3tier.domain

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

         ' private Felder
        Private _domain As IDomain

         ' zugehörige Eigenschaft
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' Standardkonstruktor
        Public Sub New()
        End Sub

         ' etwas in der Schicht [control] tun
        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
  • Die Klasse verfügt über ein privates Feld, das eine Referenz auf das Singleton vom Typ [IDomain] darstellt, welches Zugriff auf die Schicht [Domain] gewährt. Dieses Feld wird von Spring (Abhängigkeitsinjektion) beim Erstellen des Objekts initialisiert.
  • Die Methode [doSomethingInControlLayer] dekrementiert ihre Parameter und übergibt sie anschließend an die Methode [doSomethingInDomainLayer] des Singletons [domain].

7.5. Die Konfigurationsdateien [Spring]

Die Datei „[spring-config-3tier-1.xml]“ verwendet die Versionen 1 der Implementierungen:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- die DAO-Klasse -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
     <!-- die Domain-Klasse -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- die Control-Klasse -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

Die Datei [spring-config-3tier-2.xml] verwendet die Versionen 2 der Implementierungen:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- die DAO-Klasse -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
     <!-- die Klasse „domain“ -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- die Klasse „control“ -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

7.6. Das Testpaket [istia.st.spring3tier.tests]

Ein 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
         ' die Singleton-Fabriken
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' Singleton-Fabriken werden erstellt
            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()
             ' Singletons werden freigegeben
            factory1.Dispose()
            factory2.Dispose()
             ' Singleton-Fabriken werden freigegeben
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
             ' Eine Implementierung der Schnittstelle wird abgerufen IControl
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
             ' die Klasse wird verwendet
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
             ' Rufen eine weitere Implementierung der Schnittstelle ab: IControl
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
             ' wird die Klasse verwendet
            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

Die Ausführung dieses Tests liefert folgende Ergebnisse:

Image

Leser, die über eine farbige Version dieses Dokuments verfügen, werden feststellen, dass die Ergebnisse „grün“ sind, was den erfolgreichen Abschluss des Tests anzeigt.

7.7. Eine weitere Art von Spring-Konfigurationsdatei

Eine Anwendung VB.net kann mithilfe einer Datei namens [App.config] konfiguriert werden. Diese Datei wird im Stammverzeichnis des Visual Studio-Projekts abgelegt:

Image

Spring kann diese Konfigurationsdatei nutzen. Betrachten wir die folgende Datei „[App.config]“, die sich an Beispielen aus der Dokumentation zu „[Spring.net]“ orientiert:

<?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>
             <!-- eine erste Konfiguration -->
             <!-- die DAO-Klasse -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
             <!-- die Klasse „domain“ -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
             <!-- die Klasse „control“ -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
             <!-- eine zweite Konfiguration -->
             <!-- die DAO-Klasse -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
             <!-- die Domain-Klasse -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
             <!-- die Control-Klasse -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

Hinweis: Die folgenden Informationen sind mit Vorbehalt zu betrachten. Ich bin mir nicht sicher, ob ich die Bedeutung aller Elemente der oben genannten Konfigurationsdatei richtig verstanden habe.


Die Syntax XML der Datei [App.config] erfordert, dass sie der folgenden Syntax entspricht:

<configuration>
....
</configuration>

Die Verarbeitung der verschiedenen Abschnitte von [App.config] kann an externe Programme delegiert werden. Dies geschieht hier im Abschnitt [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>

Der obige Code bedeutet, dass der Abschnitt mit der Bezeichnung [spring/context] von der Klasse [Spring.Context.Support.ContextHandler] verwaltet werden muss, diesich in der Baugruppe [Spring.Core.dll] befindet, und dass der Abschnitt mit der Bezeichnung [spring/objects] von der Klasse [Spring.Context.Support.DefaultSectionHandler] verwaltet werden muss, die sich wiederum in der Baugruppe [Spring.Core.dll] befindet.

Der Abschnitt [spring/context] lautet wie folgt:

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

Darin scheint es zu heißen, dass der Abschnitt [spring/objects] der Konfigurationsdatei von der Klasse [Spring.Context.Support.XmlApplicationHandler] verwaltet werden muss, die sich in der Baugruppe [Spring.Core.dll] befindet. Diese Klasse muss eine Ressource XML nutzen, deren Speicherort [ config://spring/objects], c.a.d ist. Der Abschnitt [spring/objects] der aktuellen Konfigurationsdatei.

Im Abschnitt [spring/objects] finden wir die Spring-Syntax, an die wir uns mittlerweile gewöhnt haben.

Im Abschnitt <objects>... </objects> der Datei [App.config] haben wir zwei mögliche Konfigurationen für unsere 3-Tier-Anwendung definiert:

  • eine, die Version 1 der Schnittstellenimplementierungen verwendet
  • eine andere, die Version 2 derselben Implementierungen verwendet

Wie lässt sich nun die Datei [App.config] nutzen?

Der folgende Code zeigt eine Konsolenanwendung, die die zuvor genannte Datei [App.config] nutzt:

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()
             ' der Spring-Kontext, mit dem wir die Singletons abrufen können
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
             ' Wir rufen eine erste Implementierung der Schnittstelle ab: IControl
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
             ' Wir verwenden die Klasse
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
             ' Wir rufen eine weitere Implementierung der Schnittstelle ab: IControl
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
             ' wird die Klasse verwendet
            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

Anmerkungen:

  • In den bisher verwendeten Beispielen mit NUnit haben wir ein Objekt vom Typ [XmlObjectFactory] verwendet, um die benötigten Singletons zu erhalten. Hier wird ein Objekt vom Typ [IApplicationContext] verwendet, eine Spring-Schnittstelle. Es wird ausgehend von [App.config] mithilfe der Klasse [ConfigurationSettings] erstellt, einer Klasse, die traditionell in .NET zur Auswertung der Konfigurationsdateien verwendet wird.
  • Es wird der Handler des Abschnitts [spring/context] angefordert. Wenn man sich die Datei [App.config] ansieht, stellt man fest, dass diese vom Typ [XmlApplicationContext] ist und für die Verwaltung des Abschnitts [spring/objects] von [App.config] zuständig ist.
  • Sobald das Objekt vom Typ [IApplicationContext] abgerufen wurde, wird es wie das Objekt [XmlObjectFactory] verwendet, das wir bisher genutzt haben.

Das vorstehende Programm heißt [MainTestSpring3tier.vb] und befindet sich im Paket [tests]:

Image

Das Projekt [spring3tier] ist so konfiguriert, dass [MainTestSpring3tier]:

Image

Die Ausführung des Projekts liefert folgende Ergebnisse:

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

7.8. Conclusion

Das Spring-Framework ermöglicht echte Flexibilität sowohl bei der Architektur der Anwendungen als auch bei deren Konfiguration. Wir haben das Konzept IoC verwendet, eine der beiden Säulen von Spring. Die andere Säule ist AOP (Aspect Oriented Programming), die wir hier nicht vorgestellt haben. Sie ermöglicht es, einer Klassenmethode durch Konfiguration „Verhalten“ hinzuzufügen, ohne deren Code zu ändern. Vereinfacht gesagt ermöglicht AOP das Filtern von Aufrufen bestimmter Methoden:

  • Der Filter kann vor oder nach der Zielmethode M oder in beiden Fällen ausgeführt werden.
  • Die Methode M ignoriert die Existenz dieser Filter. Diese werden in der Spring-Konfigurationsdatei definiert.
  • Der Code der Methode M wird nicht verändert. Die Filter sind Java-Klassen, die erstellt werden müssen. Spring stellt vordefinierte Filter zur Verfügung, insbesondere zur Verwaltung der Transaktionen von SGBD.
  • Filter sind Beans und werden als solche in der Spring-Konfigurationsdatei als Beans definiert.

Ein gängiger Filter ist der Transaktionsfilter. Nehmen wir eine Methode M der Geschäftslogikschicht, die zwei untrennbare Operationen an Daten durchführt (Arbeitseinheit). Sie ruft zwei Methoden M1 und M2 der Schicht DAO auf, um diese beiden Operationen durchzuführen.

Da sich die Methode M in der Fachschicht befindet, abstrahiert sie vom Datenträger dieser Daten. Sie muss beispielsweise nicht davon ausgehen, dass sich die Daten in einem SGBD befinden und dass sie die beiden Aufrufe der Methoden M1 und M2 in eine Transaktion von SGBD einbetten muss. Es ist Aufgabe der Schicht DAO, sich um diese Details zu kümmern. Eine Lösung für das vorgenannte Problem besteht daher darin, in der Schicht DAO eine Methode anzulegen, die ihrerseits die Methoden M1 und M2 aufruft, die sie in eine Transaktion von SGBD einbinden würde.

Die Filterlösung AOP ist flexibler. Sie ermöglicht es, einen Filter zu definieren, der vor dem Aufruf von M eine Transaktion startet und nach dem Aufruf je nach Fall ein Commit oder ein Rollback durchführt.

Dieser Ansatz bietet mehrere Vorteile:

  • Sobald der Filter definiert ist, kann er auf mehrere Methoden angewendet werden, beispielsweise auf alle, die eine Transaktion benötigen
  • die so gefilterten Methoden müssen nicht neu geschrieben werden
  • Da die zu verwendenden Filter über die Konfiguration definiert werden, können sie geändert werden

Weitere Informationen: http://www.springframework.net.