Skip to content

7. Rozwiązanie

7.1. Projekt Visual Studio

Uwagi:

  • pliki niezbędne dla Springa zostały umieszczone w folderze [bin]: Spring.Core.dll, log4net.dll, Spring.Core.xml
  • Pliki konfiguracyjne Springa [spring-config-3tier-*.xml] również zostały umieszczone w folderze [bin]
  • w katalogu głównym [istia] znajdują się różne klasy aplikacji

Projekt został skonfigurowany tak, aby generować pliki DLL i [spring3tier.dll] w folderze [bin]:

Image

7.2. Pakiet [istia.st.spring3tier.dao]

Interfejs IDao:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
         ' wykonać jakąś czynność w warstwie [dao]
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Pierwszy przykład implementacji:

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

         ' wykonać jakąś czynność w warstwie [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
  • klasa nie posiada pola prywatnego
  • Metoda [doSomethingInDaoLayer] zwraca sumę swoich parametrów zgodnie z żądaniem.

Druga klasa implementacyjna:

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

         ' wykonać jakąś czynność w warstwie [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
  • klasa nie posiada pola prywatnego
  • metoda [doSomethingInDaoLayer] zwraca różnicę swoich parametrów zgodnie z żądaniem.

7.3. Pakiet [istia.st.spring3tier.domain]

Interfejs IDomain:

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
         ' wykonać jakąś czynność w warstwie [domain]
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Pierwsza klasa implementacji IDomainImpl1:

Imports istia.st.spring3tier.dao

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

         ' pola prywatne
        Private _dao As IDao

         ' powiązana właściwość
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' konstruktor domyślny
        Public Sub New()
        End Sub

         ' wykonanie jakiejś czynności w warstwie [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
  • Klasa posiada pole prywatne, które jest odwołaniem do singletonu typu [IDao], zapewniającego dostęp do warstwy [Dao]. Pole to zostanie zainicjowane przez Spring (wstrzyknięcie zależności) w momencie tworzenia obiektu.
  • Metoda [doSomethingInDomainLayer] zwiększa wartości swoich parametrów, a następnie przekazuje je do metody [doSomethingInDaoLayer] singletonu [dao]

Druga klasa implementacyjna IDomainImpl2:

Imports istia.st.spring3tier.dao

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

         ' pola prywatne
        Private _dao As IDao

         ' właściwość powiązana
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' konstruktor domyślny
        Public Sub New()
        End Sub

         ' wykonaj jakąś czynność w warstwie [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
  • Klasa posiada pole prywatne, które jest odwołaniem do singletonu typu [IDao], zapewniającego dostęp do warstwy [Dao]. Pole to zostanie zainicjowane przez Spring (wstrzyknięcie zależności) w momencie tworzenia obiektu.
  • Metoda [doSomethingInDomainLayer] zmniejsza wartości swoich parametrów, a następnie przekazuje je do metody [doSomethingInDaoLayer] singletonu [dao]

7.4. Pakiet [istia.st.spring3tier.control]

Interfejs IControl:

Namespace istia.st.spring3tier.control
    Public Interface IControl
         ' wykonanie jakiejś czynności w warstwie [control]
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Pierwsza klasa implementacji ControlImpl1:

Imports istia.st.spring3tier.domain

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

         ' pola prywatne
        Private _domain As IDomain

         ' powiązana właściwość
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' domyślny konstruktor
        Public Sub New()
        End Sub

         ' wykonanie jakiejś czynności w warstwie [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
  • Klasa posiada pole prywatne, które jest odwołaniem do singletonu typu [IDomain], zapewniającego dostęp do warstwy [Domain]. Pole to zostanie zainicjowane przez Spring (wstrzyknięcie zależności) w momencie tworzenia obiektu.
  • Metoda [doSomethingInControlLayer] zwiększa wartości swoich parametrów, a następnie przekazuje je do metody [doSomethingInDomainLayer] singletonu [domain]

Druga klasa implementacyjna IControlImpl2:

Imports istia.st.spring3tier.domain

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

         ' pola prywatne
        Private _domain As IDomain

         ' właściwość powiązana
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' konstruktor domyślny
        Public Sub New()
        End Sub

         ' wykonanie jakiejś czynności w warstwie [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
  • Klasa posiada pole prywatne, które jest odwołaniem do singletonu typu [IDomain], zapewniającego dostęp do warstwy [Domain]. Pole to zostanie zainicjowane przez Spring (wstrzyknięcie zależności) w momencie tworzenia obiektu.
  • Metoda [doSomethingInControlLayer] zmniejsza wartości swoich parametrów, a następnie przekazuje je do metody [doSomethingInDomainLayer] singletonu [domain].

7.5. Pliki konfiguracyjne [Spring]

Plik [spring-config-3tier-1.xml] wykorzystuje wersje 1 następujących implementacji:

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

Plik [spring-config-3tier-2.xml] wykorzystuje wersje 2 następujących implementacji:

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

7.6. Pakiet testów [istia.st.spring3tier.tests]

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
         ' fabryki singletonów
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' tworzymy fabryki singletonów
            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()
             ' niszczenie singletonów
            factory1.Dispose()
            factory2.Dispose()
             ' zwolnienie fabryk singletonów
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
             'pobieramy implementację interfejsu IControl
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
             ' używa się klasy
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
             ' pobieramy inną implementację interfejsu IControl
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
             ' używa się klasy
            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

Wykonanie tego testu daje następujące wyniki:

Image

Użytkownik, który posiada wersję tego dokumentu z kolorami, zauważy, że wyniki są „zielone”, co oznacza pomyślne zakończenie testu.

7.7. Inny typ pliku konfiguracyjnego Spring

Aplikację VB.net można skonfigurować za pomocą pliku o nazwie [App.config]. Plik ten umieszcza się w katalogu głównym projektu Visual Studio:

Image

Spring może wykorzystać ten plik konfiguracyjny. Rozważmy następujący plik [App.config], oparty na przykładach z dokumentacji [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>
             <!-- pierwsza konfiguracja -->
             <!-- klasa DAO -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
             <!-- klasa domain -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
             <!-- klasa control -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
             <!-- druga konfiguracja -->
             <!-- klasa DAO -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
             <!-- klasa domain -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
             <!-- klasa kontrolna -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

Uwaga: poniższe informacje podaję z zastrzeżeniem. Nie jestem pewien, czy poprawnie zrozumiałem znaczenie wszystkich elementów powyższego pliku konfiguracyjnego.


Składnia pliku XML wymaga, aby plik [App.config] był zgodny z następującą składnią:

<configuration>
....
</configuration>

Obsługę poszczególnych sekcji pliku [App.config] można powierzyć programom zewnętrznym. Tak właśnie postąpiono w sekcji [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>

Powyższy kod oznacza, że sekcja o nazwie [spring/context] powinna być zarządzana przez klasę [Spring.Context.Support.ContextHandler], którąznajduje się w zestawie [Spring.Core.dll], a sekcja o nazwie [spring/objects] powinna być zarządzana przez klasę [Spring.Context.Support.DefaultSectionHandler], która również znajduje się w zestawie [Spring.Core.dll].

Sekcja [spring/context] ma następującą treść:

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

Wydaje się, że oznacza to, iż sekcja [spring/objects] pliku konfiguracyjnego powinna być obsługiwana przez klasę [Spring.Context.Support.XmlApplicationHandler], którą można znaleźć w zestawie [Spring.Core.dll]. Klasa ta musi korzystać z zasobu XML, którego lokalizacja to [ config://spring/objects], c.a.d oraz sekcja [spring/objects] w bieżącym pliku konfiguracyjnym.

W sekcji [spring/objects] znajduje się składnia Spring, do której jesteśmy już przyzwyczajeni.

W sekcji <objects>... </objects> pliku [App.config] zdefiniowaliśmy dwie możliwe konfiguracje dla naszej aplikacji trójwarstwowej:

  • jedną, która wykorzystuje wersję 1 implementacji interfejsów
  • druga, która wykorzystuje wersję 2 tych samych implementacji

Jak zatem wykorzystać plik [App.config]?

Poniższy kod przedstawia aplikację konsolową, która wykorzystuje poprzedni plik [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()
             ' kontekst Spring, który pozwoli nam pobrać singletony
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
             ' pobieramy pierwszą implementację interfejsu IControl
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
             ' używamy klasy
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
             ' pobieramy inną implementację interfejsu IControl
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
             ' używa się klasy
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
             ' przerwa
            Console.WriteLine("Tapez [entrée] pour continuer...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

Uwagi:

  • w przykładach z obiektem NUnit, z których korzystaliśmy do tej pory, używaliśmy obiektu [XmlObjectFactory] w celu uzyskania potrzebnych nam singletonów. Tutaj używany jest obiekt typu [IApplicationContext], który jest interfejsem biblioteki Spring. Uzyskuje się go z [App.config] za pomocą klasy [ConfigurationSettings], która jest tradycyjnie wykorzystywana w .NET do obsługi plików konfiguracyjnych.
  • Wymagany jest menedżer sekcji [spring/context]. Jeśli zajrzymy do pliku [App.config], okaże się, że jest on typu [XmlApplicationContext] i odpowiada za zarządzanie sekcją [spring/objects] w pliku [App.config].
  • Po pobraniu obiektu typu [IApplicationContext] używa się go tak samo jak obiektu [XmlObjectFactory], z którego korzystaliśmy do tej pory.

Poprzedni program nosi nazwę [MainTestSpring3tier.vb] i znajduje się w pakiecie [tests]:

Image

Projekt [spring3tier] jest skonfigurowany tak, aby [MainTestSpring3tier]:

Image

Wykonanie projektu daje następujące wyniki:

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

7.8. Conclusion

Framework Spring zapewnia prawdziwą elastyczność zarówno w zakresie architektury aplikacji, jak i ich konfiguracji. Wykorzystaliśmy koncepcję IoC, jeden z dwóch filarów Springa. Drugim filarem jest AOP (programowanie zorientowane aspektowo), którego nie omówiliśmy. Pozwala ono, poprzez konfigurację, dodawać „zachowanie” do metody klasy bez modyfikowania jej kodu. W uproszczeniu AOP umożliwia filtrowanie wywołań określonych metod:

  • filtr może być wykonywany przed lub po docelowej metodzie M, albo w obu przypadkach.
  • Metoda M nie uwzględnia istnienia tych filtrów. Są one definiowane w pliku konfiguracyjnym Springa.
  • Kod metody M nie ulega zmianie. Filtry są klasami Java, które należy utworzyć. Spring udostępnia predefiniowane filtry, w szczególności do obsługi transakcji w SGBD.
  • Filtry są komponentami typu bean i jako takie są zdefiniowane w pliku konfiguracyjnym Springa jako komponenty typu bean.

Typowym filtrem jest filtr transakcyjny. Weźmy metodę M z warstwy biznesowej, która wykonuje dwie nierozerwalnie powiązane operacje na danych (jednostka pracy). Wykorzystuje ona dwie metody M1 i M2 z warstwy DAO w celu wykonania tych dwóch operacji.

Ponieważ metoda M znajduje się w warstwie biznesowej, nie zajmuje się nośnikiem tych danych. Nie musi na przykład zakładać, że dane znajdują się w SGBD i że musi umieścić dwa wywołania metod M1 oraz M2 w ramach transakcji SGBD. To warstwa DAO powinna zająć się tymi szczegółami. Rozwiązaniem powyższego problemu jest zatem utworzenie metody w warstwie DAO, która sama wywoływałaby metody M1 i M2, które to wywołania obejmowałaby w ramach transakcji SGBD.

Rozwiązanie oparte na filtrowaniu AOP jest bardziej elastyczne. Umożliwia ono zdefiniowanie filtra, który przed wywołaniem funkcji M rozpocznie transakcję, a po jej zakończeniu wykona commit lub rollback, w zależności od sytuacji.

Takie podejście ma kilka zalet:

  • po zdefiniowaniu filtra można go zastosować do wielu metod, na przykład do wszystkich, które wymagają transakcji
  • metody objęte filtrowaniem nie wymagają przepisywania
  • filtry, które mają być używane, są definiowane w konfiguracji, więc można je zmieniać

Więcej informacji: http://www.springframework.net.