Skip to content

5. Spring IoC на практиці

Тепер на прикладах розглянемо практичне застосування того, що ми розглянули раніше.

5.1. Приклад 1

Клас [Personne.vb] має такий вигляд:

Namespace istia.st.springioc.demos
    Public Class Personne

         ' приватні поля
        Private _nom As String
        Private _age As Integer

         ' конструктор за замовчуванням
        Public Sub New()
        End Sub

         ' властивості, пов'язані з приватними полями
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                _nom = Value
            End Set
        End Property

        Public Property age() As Integer
            Get
                Return _age
            End Get
            Set(ByVal Value As Integer)
                _age = Value
            End Set
        End Property

         ' ідентифікаційний рядок
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1}]", nom, age)
        End Function

         ' метод init
        Public Sub init()
            Console.WriteLine("init personne {0}", Me.ToString)
        End Sub

         ' метод close
        Public Sub close()
            Console.WriteLine("destroy personne {0}", Me.ToString)
        End Sub

    End Class
End Namespace

Цей клас містить:

  • два приватні поля «ім’я» та «вік», доступ до яких здійснюється через властивості
  • метод toString для отримання значення об’єкта [Personne] у вигляді символьного рядка
  • метод init, який буде викликаний Spring під час створення об’єкта, метод close, який буде викликаний під час знищення об’єкта

Для створення об’єктів типу [Personne] ми використаємо такий файл Spring [spring-config-1.xml]:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
</objects>

Цей файл

  • визначає два об’єкти з відповідними ключами «personne1» та «personne2» типу [Personne]
  • ініціалізує властивості [nom, age] кожної особи
  • він визначає методи, які слід викликати під час початкового створення об’єкта [init-method] та під час знищення об’єкта [destroy-method]

Для наших тестів ми будемо використовувати тестові класи NUnit. Перший з них буде таким:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo1
         ' об'єкт, що тестується
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' створюється екземпляр фабрики
            factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
             ' журнал
            Console.WriteLine("setup test")
        End Sub

        <Test()> _
        Public Sub demo()
             ' отримання об’єктів [Personne] із файлу Spring за їхнім ключем
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)
            Console.WriteLine("personne1=" + personne1.ToString())
            Dim personne2 As Personne = CType(factory.GetObject("personne2"), Personne)
            Console.WriteLine("personne2=" + personne2.ToString())
            personne2 = CType(factory.GetObject("personne2"), Personne)
            Console.WriteLine("personne2=" + personne2.ToString())
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' знищення синглетонів
            factory.Dispose()
             ' звільняється ресурс фабрики
            factory = Nothing
             ' моніторинг
            Console.WriteLine("teardown test")
        End Sub

    End Class
End Namespace

Коментарі:

  • щоб отримати об’єкти, визначені у файлі [spring-config-1.xml], ми використовуємо об’єкт типу [XmlObjectFactory]. Існують й інші типи «фабрик», що дозволяють отримати доступ до синглетонів з файлу конфігурації. Об’єкт [XmlObjectFactory] отримується в методі атрибута [SetUp] тестового класу та зберігається у приватній змінній. Нагадаємо, що метод, який має атрибут <Setup()>, виконується перед кожним тестом.
  • файл [spring-config-1.xml] буде розміщено в папці [bin] програми
  • Spring може використовувати конфігураційні файли різних форматів. Об’єкт [XmlObjectFactory] дозволяє аналізувати конфігураційний файл у форматі XML.
  • Обробка файлу Spring дає об’єкт типу [XmlObjectFactory], у даному випадку — об’єкт factory. За допомогою цього об’єкта синглтон, ідентифікований ключем C, отримується за допомогою factory.getObject(C).
  • Метод [demo] запитує та відображає значення синглетонів із ключами «personne1» та «personne2».

Структура проєкту Visual Studio така:

Image

Коментарі:

  • проект VS називається [demo1]
  • папка [istia] містить вихідні коди. Скомпільовані коди будуть розміщені в папці [bin] у DLL [demo1.dll]:

Image

  • файл [spring-config-1.xml] знаходиться у папці [bin] цього проєкту.
  • папка [bin] містить файли, необхідні для роботи додатка:
    • Spring.Core.dll, Spring.Core.xml — для класів Spring
    • log4net.dll — для журналів Spring
    • demo1.dll — це файл DLL, створений під час генерації проєкту

Файл DLL, створений під час генерації проєкту, завантажується в графічний інструмент тестування [Nunit-Gui 2.2]. Цей інструмент автоматично відображає класи NUnit, присутні у файлі DLL:

Image

Виконання методу [demo] тесту NUnit дає результати, зазначені вище та наведені нижче:

***** istia.st.springioc.tests.NunitTestSpringIocDemo1.demo
setup test
init personne [Simon,40]
personne1=[Simon,40]
init personne [Brigitte,20]
personne2=[Brigitte,20]
personne2=[Brigitte,20]
destroy personne [Simon,40]
destroy personne [Brigitte,20]
teardown test

Коментарі:

  • рядок 2: тест починається з виконання методу атрибута <Setup()>
  • рядок 3: операція
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)

призвела до створення біна [personne1]. Оскільки у визначенні синглтона [personne1] було вказано [init-method="init"], було виконано метод [init] створеного об’єкта [Personne]. Відображається відповідне повідомлення.

init personne [Simon,40]
  • рядок 4: операція
            Console.WriteLine("personne1=" + personne1.ToString())

призвела до відображення значення створеного об’єкта [Personne].

personne1=[Simon,40]
  • рядки 5–6: те саме повторюється для ключового біна [personne2].
init personne [Brigitte,20]
personne2=[Brigitte,20]
  • рядок 7: остання операція
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())

дала лише один рядок на екрані:

personne2=[Brigitte,20]

Отже, вона не спричинила створення нового об’єкта типу [Personne]. Якби це сталося, ми б побачили виведення методу [init], чого тут не сталося. Це і є принцип синглтона. Spring за замовчуванням створює лише один екземпляр бінів зі свого файлу конфігурації. Це служба об’єктних посилань. Якщо до неї звернутися з запитом на посилання на об’єкт, який ще не створено, вона створює його та повертає посилання. Якщо об’єкт уже створено, Spring просто надає посилання на нього.

  • рядки 8–10: виконується метод атрибута <TearDown()> (рядок 10). Усередині нього виконується операція
             ' знищуються синглтони
            factory.Dispose()

призводить до знищення синглетонів, створених Spring. Це спричиняє виконання для кожного з них їхнього методу [close], оскільки у файлі конфігурації для кожного з них було вказано: «destroy-method=close». Про це свідчать такі виводи:

destroy personne [Simon,40]
destroy personne [Brigitte,20]

Оскільки ми вже ознайомилися з основами конфігурації Spring, відтепер наші пояснення будуть дещо швидшими.

5.2. Приклад 2

Розглянемо новий клас [Voiture]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class Voiture
         ' приватні поля
        Private _marque As String
        Private _type As String
        Private _propriétaire As Personne

         ' публічні властивості
        Public Property marque() As String
            Get
                Return _marque
            End Get
            Set(ByVal Value As String)
                _marque = Value
            End Set
        End Property

        Public Property type() As String
            Get
                Return _type
            End Get
            Set(ByVal Value As String)
                _type = Value
            End Set
        End Property

        Public Property propriétaire() As Personne
            Get
                Return _propriétaire
            End Get
            Set(ByVal Value As Personne)
                _propriétaire = Value
            End Set
        End Property

         ' конструктор за замовчуванням
        Public Sub New()

        End Sub

         ' конструктор з трьома параметрами
        Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
             ' ініціалізуємо приватні поля через їхні відповідні властивості
            With Me
                .marque = marque
                .type = type
                .propriétaire = propriétaire
            End With
        End Sub

         ' ідентифікаційний рядок об’єкта «Автомобіль»
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
        End Function

         ' методи init-destroy
        Public Sub init()
            Console.WriteLine("init voiture {0}", Me.ToString)
        End Sub

        Public Sub destroy()
            Console.WriteLine("destroy voiture {0}", Me.ToString)
        End Sub

    End Class
End Namespace

Цей клас містить:

  • три приватні поля _type, _marque та _propriétaire. Ці поля можна ініціалізувати та зчитувати за допомогою публічних властивостей. Їх також можна ініціалізувати за допомогою конструктора Voiture(String, String, Personne). Клас також має конструктор без аргументів, який дозволяє спочатку створити неініціалізований об’єкт [Voiture], а потім ініціалізувати його за допомогою його публічних властивостей.
  • метод toString для отримання значення об’єкта [Voiture] у вигляді символьного рядка
  • метод init, який буде викликаний Spring одразу після створення об’єкта, метод destroy, який буде викликаний під час знищення об’єкта

Для створення об’єктів типу [Voiture] ми використаємо такий файл Spring [spring-config-2.xml]:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- осіб -->
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
     <!-- автомобіль -->
    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2"/>
        </constructor-arg>
    </object>
</objects>

Цей файл додає до попередніх визначень об’єкт ключа «voiture1» типу [Voiture]. Щоб ініціалізувати цей об’єкт, можна було б написати:

    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2"/>
        </constructor-arg>
    </object>

Замість того, щоб вибрати цей метод, який вже було представлено, ми вирішили тут використати конструктор Voiture(String, String, Personne) цього класу. Крім того, синглтон [voiture1] визначає метод, який слід викликати під час початкового створення об’єкта [init-method], та метод, який слід викликати під час знищення об’єкта [destroy-method].

Для наших тестів ми будемо використовувати такий тестовий клас NUnit [NunitTestSpringIocDemo2]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo2
         ' фабрика синглетонів
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' створюється екземпляр фабрики
            factory = New XmlObjectFactory(New FileStream("spring-config-2.xml", FileMode.Open))
             ' журнал
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
    Public Sub destroy()
             ' знищуємо синглтони
            factory.Dispose()
             ' звільняємо фабрику
            factory = Nothing
             ' відстеження
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
        Public Sub demo2()
             ' отримання синглтона [voiture1]
            Dim voiture1 As Voiture = CType(factory.GetObject("voiture1"), Voiture)
            Console.WriteLine("Voiture1=[{0}]", voiture1)
        End Sub

    End Class
End Namespace

Коментарі:

  • методи атрибутів <Setup()> та <TearDown()> залишаються без змін.
  • Метод [demo2] отримує посилання на синглтон [voiture1] і відображає його.

Структура проєкту Visual Studio залишилася такою, як і раніше. З’явилися лише два нових класи та файл конфігурації Spring:

Image

Виконання тесту NUnit дає такі результати:

Image

Прокоментуємо виводи на екрані:

1
2
3
4
5
6
7
8
***** istia.st.springioc.tests.NunitTestSpringIocDemo2.demo2
setup test
init personne [Brigitte,20]
init voiture [Peugeot,307,[Brigitte,20]]
Voiture1=[[Peugeot,307,[Brigitte,20]]]
destroy voiture [Peugeot,307,[Brigitte,20]]
destroy personne [Brigitte,20]
teardown test

Коментарі:

  • рядок 2: метод атрибута [Setup], що виконується перед кожним тестом, у даному випадку — [demo]
  • рядок 3: Spring починає створення синглтона [voiture1]. Він має залежність від синглтона [personne2], якого ще не існує. Тому останній створюється, і виконується його метод [init].
  • рядок 4: тепер можна створити синглтон [voiture1]. Після цього виконується його метод [init].
  • рядок 5: метод [demo2] виводить значення синглтона [voiture1]
  • рядки 6–8: виконується метод [TearDown] тесту. Сінглтони знищуються, що призводить до виконання їхніх методів [destroy]

5.3. Приклад 3

Вводимо новий клас [GroupePersonnes] наступного вигляду:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class GroupePersonnes
         ' приватні поля
        Private _membres() As Personne
        Private _groupesDeTravail As Hashtable

         ' публічні властивості
        Public Property membres() As Personne()
            Get
                Return _membres
            End Get
            Set(ByVal Value() As Personne)
                _membres = Value
            End Set
        End Property

        Public Property groupesDeTravail() As Hashtable
            Get
                Return _groupesDeTravail
            End Get
            Set(ByVal Value As Hashtable)
                _groupesDeTravail = Value
            End Set
        End Property

         ' конструктор за замовчуванням
        Public Sub New()

        End Sub

         ' ідентифікаційний рядок об’єкта GroupeDePersonnes
        Public Overrides Function tostring() As String
             ' пробігаємо список членів групи
            Dim identité As String = "[membres=("
            Dim i As Integer
            For i = 0 To membres.Length - 2
                identité += membres(i).ToString + ","
            Next
            identité += membres(i).ToString + "), groupes de travail=("
             ' проглядається словник робочих груп
            Dim clés As IEnumerator = groupesDeTravail.Keys.GetEnumerator
            Dim clé As Object
            While clés.MoveNext
                clé = clés.Current
                identité += String.Format("[{0},{1}] ", clé, groupesDeTravail(clé))
            End While
             ' повертається результат
            Return identité + "]"
        End Function

         ' методи init-destroy
        Public Sub init()
            Console.WriteLine("init GroupeDePersonnes {0}", Me.ToString)
        End Sub

        Public Sub destroy()
            Console.WriteLine("destroy GroupeDePersonnes {0}", Me.ToString)
        End Sub

    End Class
End Namespace

Два його приватні елементи:

_membres: масив осіб, що входять до групи

_groupesDeTravail: словник, що прив’язує особу до робочої групи

Ці приватні елементи стають доступними через публічні властивості. Тут ми прагнемо показати, як Spring дозволяє ініціалізувати складні об’єкти, такі як об’єкти, що містять поля типу масиву або словника.

Файл конфігурації Spring [spring-config-3.xml] матиме такий вигляд:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- осіб -->
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
     <!-- автомобіль -->
    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2" />
        </constructor-arg>
    </object>
     <!-- група людей -->
    <object id="groupe1" type="istia.st.springioc.demos.GroupePersonnes" init-method="init"
        destroy-method="destroy">
        <property name="membres">
            <list>
                <ref object="personne1" />
                <ref object="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <dictionary>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </dictionary>
        </property>
    </object>
</objects>
  1. тег <list> дозволяє ініціалізувати поле типу масиву або типу IList різними значеннями.
  1. тег <dictionary> дозволяє зробити те саме з полем, що реалізує інтерфейс IDictionary

Для наших тестів ми будемо використовувати такий тестовий клас NUnit [NunitTestSpringIocDemo3]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo3
         ' об'єкт, що тестується
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' створюється екземпляр фабрики
            factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
             ' журнал
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' знищуємо синглтони
            factory.Dispose()
             ' відстеження
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
    Public Sub demo3()
             ' отримання синглтона [groupe1]
            Dim groupe1 As GroupePersonnes = CType(factory.GetObject("groupe1"), GroupePersonnes)
            Console.WriteLine("groupe1={0}", groupe1)
        End Sub

    End Class
End Namespace

Метод [demo3] отримує синглтон [groupe1] і відображає його.

Структура проекту Visual Studio залишається такою ж, як і раніше, за винятком того, що до неї додано два класи та один додатковий файл конфігурації.

Image

Виконання методу [demo3] у тесті NUnit дає такі результати:

Image

Коментарі:

***** istia.st.springioc.tests.NunitTestSpringIocDemo3.demo3
setup test
init personne [Simon,40]
init personne [Brigitte,20]
init GroupeDePersonnes [membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
groupe1=[membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
destroy GroupeDePersonnes [membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
destroy personne [Simon,40]
destroy personne [Brigitte,20]
teardown test
  • рядок 2: метод атрибута [Setup], що виконується перед кожним тестом, у даному випадку [demo]
  • рядки 3–4: Spring починає створення синглтона [groupe1]. Він має залежність від синглтонів [personne1, personne2], яких ще не існує. Ці синглтони створюються, і їхні методи [init] виконуються.
  • рядок 5: тепер можна створити синглтон [groupe1]. Після цього виконується його метод [init].
  • рядок 6: метод [demo3] виводить значення синглтона [groupe1]
  • рядки 7–10: виконується метод [TearDown] тесту. Сінглтони знищуються, що призводить до виконання їхніх методів [destroy]