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

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:

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:

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ą:
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]:

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

Wykonanie projektu daje następujące wyniki:
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.





