7. Рішення
7.1. Проєкт Visual Studio
![]() | ![]() |
Коментарі:
- файли, необхідні для Spring, було розміщено в папці [bin]: Spring.Core.dll, log4net.dll, Spring.Core.xml
- конфігураційні файли Spring [spring-config-3tier-*.xml] також розміщено у папці [bin]
- у кореневій папці [istia] містяться різні класи додатка
Проєкт налаштовано на генерацію DLL [spring3tier.dll] у папці [bin]:

7.2. Пакет [istia.st.spring3tier.dao]
Інтерфейс IDao:
Namespace istia.st.spring3tier.dao
Public Interface IDao
' зробити щось у шарі [dao]
Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
End Interface
End Namespace
Приклад реалізації першого класу:
Namespace istia.st.spring3tier.dao
Public Class DaoImpl1
Implements istia.st.spring3tier.dao.IDao
' зробити щось у шарі [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
- клас не має приватного поля
- Метод [doSomethingInDaoLayer] повертає суму своїх параметрів, як і було задано.
Другий клас реалізації:
Namespace istia.st.spring3tier.dao
Public Class DaoImpl2
Implements istia.st.spring3tier.dao.IDao
' зробити щось у шарі [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
- клас не має приватного поля
- метод [doSomethingInDaoLayer] повертає різницю своїх параметрів, як і було задано.
7.3. Пакет [istia.st.spring3tier.domain]
Інтерфейс IDomain:
Namespace istia.st.spring3tier.domain
Public Interface IDomain
' виконати якусь дію в шарі [domain]
Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
End Interface
End Namespace
Перший клас реалізації IDomainImpl1:
Imports istia.st.spring3tier.dao
Namespace istia.st.spring3tier.domain
Public Class DomainImpl1
Implements istia.st.spring3tier.domain.IDomain
' приватні поля
Private _dao As IDao
' пов’язана властивість
Public WriteOnly Property dao() As IDao
Set(ByVal Value As IDao)
_dao = Value
End Set
End Property
' конструктор за замовчуванням
Public Sub New()
End Sub
' виконати якусь дію в шарі [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
- клас має приватне поле, яке є посиланням на синглтон типу [IDao], що надає доступ до шару [Dao]. Це поле буде ініціалізовано Spring (ін’єкція залежностей) під час створення об’єкта.
- Метод [doSomethingInDomainLayer] збільшує значення своїх параметрів, а потім передає їх методу [doSomethingInDaoLayer] синглтона [dao]
Другий клас реалізації IDomainImpl2:
Imports istia.st.spring3tier.dao
Namespace istia.st.spring3tier.domain
Public Class DomainImpl2
Implements istia.st.spring3tier.domain.IDomain
' приватні поля
Private _dao As IDao
' пов'язана властивість
Public WriteOnly Property dao() As IDao
Set(ByVal Value As IDao)
_dao = Value
End Set
End Property
' конструктор за замовчуванням
Public Sub New()
End Sub
' виконати якусь дію в шарі [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
- клас має приватне поле, яке є посиланням на синглтон типу [IDao], що надає доступ до шару [Dao]. Це поле буде ініціалізовано Spring (ін’єкція залежностей) під час створення об’єкта.
- Метод [doSomethingInDomainLayer] зменшує значення своїх параметрів, а потім передає їх методу [doSomethingInDaoLayer] синглтона [dao]
7.4. Пакет [istia.st.spring3tier.control]
Інтерфейс IControl:
Namespace istia.st.spring3tier.control
Public Interface IControl
' виконати якусь дію в шарі [control]
Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
End Interface
End Namespace
Перший клас реалізації ControlImpl1:
Imports istia.st.spring3tier.domain
Namespace istia.st.spring3tier.control
Public Class ControlImpl1
Implements istia.st.spring3tier.control.IControl
' приватні поля
Private _domain As IDomain
' пов'язана властивість
Public WriteOnly Property domain() As IDomain
Set(ByVal Value As IDomain)
_domain = Value
End Set
End Property
' конструктор за замовчуванням
Public Sub New()
End Sub
' виконати якусь дію в шарі [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
- клас має приватне поле, яке є посиланням на синглтон типу [IDomain], що надає доступ до шару [Domain]. Це поле буде ініціалізовано Spring (ін’єкція залежностей) під час створення об’єкта.
- Метод [doSomethingInControlLayer] збільшує значення своїх параметрів, а потім передає їх методу [doSomethingInDomainLayer] синглтона [domain]
Другий клас реалізації IControlImpl2:
Imports istia.st.spring3tier.domain
Namespace istia.st.spring3tier.control
Public Class ControlImpl2
Implements istia.st.spring3tier.control.IControl
' приватні поля
Private _domain As IDomain
' пов'язана властивість
Public WriteOnly Property domain() As IDomain
Set(ByVal Value As IDomain)
_domain = Value
End Set
End Property
' конструктор за замовчуванням
Public Sub New()
End Sub
' виконати якусь дію в шарі [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
- клас має приватне поле, яке є посиланням на синглтон типу [IDomain], що надає доступ до шару [Domain]. Це поле буде ініціалізовано Spring (ін’єкція залежностей) під час створення об’єкта.
- Метод [doSomethingInControlLayer] зменшує значення своїх параметрів, а потім передає їх методу [doSomethingInDomainLayer] синглтона [domain].
7.5. Конфігураційні файли [Spring]
Файл [spring-config-3tier-1.xml] використовує версії 1 таких реалізацій:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<!-- клас DAO -->
<object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
<!-- клас домену -->
<object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
<property name="dao">
<ref object="dao" />
</property>
</object>
<!-- клас control -->
<object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
<property name="domain">
<ref object="domain" />
</property>
</object>
</objects>
Файл [spring-config-3tier-2.xml] використовує версії 2 таких реалізацій:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<!-- клас dao -->
<object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
<!-- клас domain -->
<object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
<property name="dao">
<ref object="dao" />
</property>
</object>
<!-- клас control -->
<object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
<property name="domain">
<ref object="domain" />
</property>
</object>
</objects>
7.6. Пакет тестів [istia.st.spring3tier.tests]
Тест 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
' фабрики синглтонів
Private factory1 As XmlObjectFactory
Private factory2 As XmlObjectFactory
<SetUp()> _
Public Sub init()
' створюються фабрики синглетонів
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()
' знищення синглетонів
factory1.Dispose()
factory2.Dispose()
' звільнення фабрик синглетонів
factory1 = Nothing
factory2 = Nothing
End Sub
<Test()> _
Public Sub test()
'отримуємо реалізацію інтерфейсу IControl
Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
' використовуємо клас
Dim a1 As Integer = 10, b1 As Integer = 20
Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
Assert.AreEqual(34, res1)
' отримуємо іншу реалізацію інтерфейсу IControl
Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
' використовується клас
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
Виконання цього тесту дає такі результати:

Користувач, який має кольорову версію цього документа, побачить, що результати позначені «зеленим» кольором, що свідчить про успішне виконання тесту.
7.7. Інший тип конфігураційного файлу Spring
Додаток VB.net можна налаштувати за допомогою файлу з назвою [App.config]. Цей файл розміщується в кореневому каталозі проєкту Visual Studio:

Spring може використовувати цей файл конфігурації. Розглянемо наступний файл [App.config], створений на основі прикладів із документації до [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>
<!-- перша конфігурація -->
<!-- клас DAO -->
<object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
<!-- клас domain -->
<object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
<property name="dao">
<ref object="dao1" />
</property>
</object>
<!-- клас control -->
<object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
<property name="domain">
<ref object="domain1" />
</property>
</object>
<!-- друга конфігурація -->
<!-- клас DAO -->
<object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
<!-- клас domain -->
<object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
<property name="dao">
<ref object="dao2" />
</property>
</object>
<!-- клас управління -->
<object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
<property name="domain">
<ref object="domain2" />
</property>
</object>
</objects>
</spring>
</configuration>
Примітка: наведена нижче інформація надається з застереженням. Я не впевнений, що правильно зрозумів значення всіх елементів наведеного вище конфігураційного файлу.
Синтаксис XML файлу [App.config] вимагає дотримання такого синтаксису:
Обробку різних розділів [App.config] можна делегувати зовнішнім програмам. Саме це зроблено тут у розділі [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>
Наведений вище код означає, що секція з назвою [spring/context] має управлятися класом [Spring.Context.Support.ContextHandler], якийзнаходиться в збірці [Spring.Core.dll], а розділ із назвою [spring/objects] має оброблятися класом [Spring.Context.Support.DefaultSectionHandler], який також знаходиться у збірці [Spring.Core.dll].
Розділ [spring/context] має такий вигляд:
<spring>
<context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
<resource uri="config://spring/objects" />
</context>
...
</spring>
Здається, тут йдеться про те, що розділ [spring/objects] конфігураційного файлу має оброблятися класом [Spring.Context.Support.XmlApplicationHandler], який міститься в збірці [Spring.Core.dll]. Цей клас повинен використовувати ресурс XML, розташування якого — [ config://spring/objects], c.a.d — у розділі [spring/objects] поточного конфігураційного файлу.
У розділі [spring/objects] ми бачимо синтаксис Spring, до якого ми вже звикли.
У розділі <objects>... </objects> файлу [App.config] ми визначили дві можливі конфігурації для нашого трирівневого додатка:
- одна, що використовує версію 1 реалізацій інтерфейсів
- інша — використовує версію 2 тих самих реалізацій
Як же тепер використовувати файл [App.config]?
Наступний код демонструє консольний додаток, який використовує попередній файл [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()
' контекст Spring, який дозволить нам отримати синглтони
Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
' отримуємо першу реалізацію інтерфейсу IControl
Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
' використовуємо клас
Dim a1 As Integer = 10, b1 As Integer = 20
Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
' отримуємо іншу реалізацію інтерфейсу IControl
Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
' використовується клас
Dim a2 As Integer = 10, b2 As Integer = 20
Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
' пауза
Console.WriteLine("Tapez [entrée] pour continuer...")
Console.ReadLine()
End Sub
End Module
End Namespace
Коментарі:
- у прикладах NUnit, які ми використовували досі, ми застосовували об’єкт [XmlObjectFactory] для отримання необхідних нам синглетонів. Тут використовується об’єкт типу [IApplicationContext] — інтерфейс Spring. Його можна отримати з [App.config] за допомогою класу [ConfigurationSettings], який традиційно використовується в .NET для роботи з конфігураційними файлами.
- Викликається менеджер розділу [spring/context]. Якщо звернутися до файлу [App.config], то можна побачити, що він має тип [XmlApplicationContext] і відповідає за управління розділом [spring/objects] у [App.config].
- Після отримання об’єкта типу [IApplicationContext] його використовують так само, як об’єкт [XmlObjectFactory], який ми використовували до цього моменту.
Попередня програма має назву [MainTestSpring3tier.vb] і розміщена в пакеті [tests]:

Проєкт [spring3tier] налаштований так, щоб [MainTestSpring3tier]:

Виконання проекту дає такі результати:
7.8. Conclusion
Фреймворк Spring забезпечує справжню гнучкість як в архітектурі додатків, так і в їхній конфігурації. Ми використовували концепцію IoC, одну з двох основ Spring. Іншим стовпом є AOP (аспектно-орієнтоване програмування), яке ми не розглядали. Воно дозволяє шляхом конфігурації додавати «поведінку» до методу класу, не змінюючи його код. Схематично AOP дозволяє фільтрувати виклики певних методів:
![]() |
- фільтр може виконуватися до або після цільового методу M, або в обох випадках.
- Метод M ігнорує існування цих фільтрів. Вони визначаються у файлі конфігурації Spring.
- Код методу M не змінюється. Фільтри — це Java-класи, які потрібно створити. Spring надає попередньо визначені фільтри, зокрема для управління транзакціями SGBD.
- Фільтри є бінами і, як такі, визначаються у файлі конфігурації Spring як біни.
Поширеним фільтром є транзакційний фільтр. Розглянемо метод M бізнес-шару, який виконує дві нерозривно пов’язані операції над даними (одиниця роботи). Він викликає два методи M1 та M2 з шару DAO для виконання цих двох операцій.
![]() |
Оскільки метод M знаходиться в бізнес-шарі, він абстрагується від носія цих даних. Наприклад, йому не потрібно припускати, що дані знаходяться в SGBD і що їй потрібно помістити обидва виклики методів M1 та M2 у транзакцію SGBD. Саме рівень DAO повинен займатися цими деталями. Тож рішенням попередньої проблеми є створення методу в шарі DAO, який сам викликатиме методи M1 та M2, які він об’єднає в транзакцію SGBD.
![]() |
Рішення щодо фільтрації AOP є більш гнучким. Воно дозволить визначити фільтр, який перед викликом M розпочне транзакцію, а після виклику виконає фіксацію (commit) або відкат (rollback) залежно від ситуації.
![]() |
Цей підхід має кілька переваг:
- після визначення фільтра його можна застосувати до кількох методів, наприклад, до всіх тих, що потребують транзакції
- методи, відфільтровані таким чином, не потрібно переписувати
- оскільки фільтри, що використовуються, визначаються в конфігурації, їх можна змінювати
Додаткова інформація: http://www.springframework.net.





