Skip to content

5. Spring IoC w praktyce

Teraz, posługując się przykładami, omówimy praktyczne zastosowanie tego, co omówiliśmy wcześniej.

5.1. Przykład 1

Klasa [Personne.vb] wygląda następująco:

Namespace istia.st.springioc.demos
    Public Class Personne

         ' pola prywatne
        Private _nom As String
        Private _age As Integer

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

         ' właściwości powiązane z polami prywatnymi
        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

         ' ciąg identyfikacyjny
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1}]", nom, age)
        End Function

         ' metoda init
        Public Sub init()
            Console.WriteLine("init personne {0}", Me.ToString)
        End Sub

         ' metoda close
        Public Sub close()
            Console.WriteLine("destroy personne {0}", Me.ToString)
        End Sub

    End Class
End Namespace

Klasa zawiera:

  • dwa pola prywatne „nazwa” i „wiek”, dostępne poprzez właściwości
  • metodę toString służącą do pobrania wartości obiektu [Personne] w postaci ciągu znaków
  • metodę init, która zostanie wywołana przez Spring podczas tworzenia obiektu, oraz metodę close, która zostanie wywołana podczas niszczenia obiektu

Aby utworzyć obiekty typu [Personne], wykorzystamy następujący plik 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>

Ten plik

  • definiuje dwa obiekty o nazwach „osoba1” i „osoba2” typu [Personne]
  • inicjuje właściwości [nom, age] każdej osoby
  • definiuje metody, które mają zostać wywołane podczas początkowego tworzenia obiektu [init-method] oraz podczas niszczenia obiektu [destroy-method]

Do naszych testów wykorzystamy klasy testowe NUnit. Pierwsza z nich będzie wyglądać następująco:

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
         ' obiekt do testowania
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' tworzymy instancję fabryki
            factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
             ' log
            Console.WriteLine("setup test")
        End Sub

        <Test()> _
        Public Sub demo()
             ' pobieranie obiektów [Personne] z pliku Spring na podstawie ich kluczy
            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()
             ' niszczymy singletony
            factory.Dispose()
             ' zwolnienie zasobu fabryki
            factory = Nothing
             ' monitorowanie
            Console.WriteLine("teardown test")
        End Sub

    End Class
End Namespace

Uwagi:

  • aby uzyskać obiekty zdefiniowane w pliku [spring-config-1.xml], używamy obiektu typu [XmlObjectFactory]. Istnieją inne typy „fabryk”, które umożliwiają dostęp do singletonów z pliku konfiguracyjnego. Obiekt [XmlObjectFactory] jest pobierany w metodzie atrybutu [SetUp] klasy testowej i zapisywany w zmiennej prywatnej. Należy pamiętać, że metoda opatrzona atrybutem <Setup()> jest wykonywana przed każdym testem.
  • Plik [spring-config-1.xml] zostanie umieszczony w folderze [bin] aplikacji
  • Spring może korzystać z plików konfiguracyjnych w różnych formatach. Obiekt [XmlObjectFactory] umożliwia analizę pliku konfiguracyjnego w formacie XML.
  • Przetworzenie pliku Spring zwraca obiekt typu [XmlObjectFactory], w tym przypadku obiekt typu factory. Za pomocą tego obiektu można uzyskać singleton identyfikowany przez klucz C, stosując wyrażenie factory.getObject(C).
  • Metoda [demo] pobiera i wyświetla wartości singletonów o kluczach „personne1” i „personne2”.

Struktura projektu w Visual Studio wygląda następująco:

Image

Uwagi:

  • projekt VS nosi nazwę [demo1]
  • folder [istia] zawiera kod źródłowy. Skompilowany kod trafi do folderu [bin] w ramach DLL [demo1.dll]:

Image

  • Plik [spring-config-1.xml] znajduje się w folderze [bin] projektu.
  • Folder [bin] zawiera pliki niezbędne dla aplikacji:
    • Spring.Core.dll, Spring.Core.xml dla klas Spring
    • log4net.dll dla logów Spring
    • demo1.dll, który jest plikiem DLL wygenerowanym podczas tworzenia projektu

Plik DLL oraz [demo1.dll], wygenerowane podczas tworzenia projektu, są ładowane do graficznego narzędzia testowego [Nunit-Gui 2.2]. Narzędzie to automatycznie wyświetla klasy NUnit zawarte w pliku DLL:

Image

Wykonanie metody [demo] w teście NUnit daje wyniki wskazane powyżej i przedstawione poniżej:

***** 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

Uwagi:

  • wiersz 2: test rozpoczyna się od wykonania metody atrybutu <Setup()>
  • wiersz 3: operacja
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)

spowodowała utworzenie bean [personne1]. Ponieważ w definicji singletonu [personne1] wpisano [init-method="init"], wykonano metodę [init] utworzonego obiektu [Personne]. Wyświetlany jest odpowiedni komunikat.

init personne [Simon,40]
  • wiersz 4: operacja
            Console.WriteLine("personne1=" + personne1.ToString())

spowodowała wyświetlenie wartości utworzonego obiektu [Personne].

personne1=[Simon,40]
  • wiersze 5–6: to samo zjawisko powtarza się w przypadku fasoli klucza [personne2].
init personne [Brigitte,20]
personne2=[Brigitte,20]
  • wiersz 7: ostatnia operacja
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())

dała tylko jedną linię wyświetlania:

personne2=[Brigitte,20]

Nie spowodowała więc utworzenia nowego obiektu typu [Personne]. Gdyby tak było, wyświetlono by metodę [init], co jednak nie ma miejsca w tym przypadku. Na tym polega zasada singletonu. Spring domyślnie tworzy tylko jeden egzemplarz beanów z pliku konfiguracyjnego. Jest to usługa referencji obiektowych. Jeśli poprosimy go o referencję do obiektu, który nie został jeszcze utworzony, Spring tworzy go i zwraca referencję. Jeśli obiekt został już utworzony, Spring po prostu zwraca referencję do niego.

  • wiersze 8–10: wykonywana jest metoda atrybutu <TearDown()> (wiersz 10). W jej obrębie operacja
             ' niszczymy singletony
            factory.Dispose()

powoduje zniszczenie singletonów utworzonych przez Spring. Powoduje to, że dla każdego z nich wykonywana jest ich metoda [close], ponieważ w pliku konfiguracyjnym dla każdego z nich wpisano: „destroy-method=close”. Wskazują na to następujące komunikaty:

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

Ponieważ podstawy konfiguracji Springa zostały już omówione, od tej pory nasze wyjaśnienia będą przebiegały nieco szybciej.

5.2. Przykład 2

Rozważmy następującą nową klasę [Voiture]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class Voiture
         ' pola prywatne
        Private _marque As String
        Private _type As String
        Private _propriétaire As Personne

         ' właściwości publiczne
        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

         ' konstruktor domyślny
        Public Sub New()

        End Sub

         ' konstruktor z trzema parametrami
        Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
             ' inicjujemy pola prywatne za pomocą powiązanych z nimi właściwości
            With Me
                .marque = marque
                .type = type
                .propriétaire = propriétaire
            End With
        End Sub

         ' ciąg identyfikacyjny obiektu Samochód
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
        End Function

         ' metody 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

Klasa zawiera:

  • trzy pola prywatne: _type, _marque oraz _propriétaire. Pola te można inicjować i odczytywać za pomocą właściwości publicznych. Można je również zainicjować za pomocą konstruktora Voiture(String, String, Personne). Klasa posiada również konstruktor bez argumentów, który pozwala najpierw utworzyć niezinicjalizowany obiekt [Voiture], a następnie zainicjować go za pomocą jego właściwości publicznych.
  • metodę toString służącą do pobrania wartości obiektu [Voiture] w postaci ciągu znaków
  • metodę init, która zostanie wywołana przez Springa zaraz po utworzeniu obiektu, oraz metodę destroy, która zostanie wywołana podczas niszczenia obiektu

Aby utworzyć obiekty typu [Voiture], wykorzystamy następujący plik 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>
     <!-- osób -->
    <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>
     <!-- samochód -->
    <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>

Ten plik dodaje do poprzednich definicji obiekt klucza „samochód1” typu [Voiture]. Aby zainicjować ten obiekt, można by napisać:

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

Zamiast skorzystać z tej metody, którą już przedstawiliśmy, zdecydowaliśmy się tutaj użyć konstruktora Voiture(String, String, Personne) tej klasy. Ponadto singleton [voiture1] definiuje metodę, która ma zostać wywołana podczas początkowego tworzenia obiektu [init-method], oraz metodę, która ma zostać wywołana podczas niszczenia obiektu [destroy-method].

Do naszych testów wykorzystamy następującą klasę testową 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
         ' fabryka singletonów
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' tworzymy instancję fabryki
            factory = New XmlObjectFactory(New FileStream("spring-config-2.xml", FileMode.Open))
             ' log
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
    Public Sub destroy()
             ' niszczymy singletony
            factory.Dispose()
             ' zwolnienie fabryki
            factory = Nothing
             ' monitorowanie
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
        Public Sub demo2()
             ' pobieranie singletonu [voiture1]
            Dim voiture1 As Voiture = CType(factory.GetObject("voiture1"), Voiture)
            Console.WriteLine("Voiture1=[{0}]", voiture1)
        End Sub

    End Class
End Namespace

Uwagi:

  • metody atrybutów <Setup()> i <TearDown()> pozostają bez zmian.
  • Metoda [demo2] pobiera odwołanie do singletonu [voiture1] i wyświetla go.

Struktura projektu Visual Studio pozostaje taka sama jak poprzednio. Pojawiły się jedynie dwie nowe klasy oraz plik konfiguracyjny Spring:

Image

Wykonanie testu NUnit daje następujące wyniki:

Image

Omówmy wyświetlane ekrany:

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

Komentarze:

  • wiersz 2: metoda atrybutu [Setup] wykonywana przed każdym testem, w tym przypadku [demo]
  • wiersz 3: Spring rozpoczyna tworzenie singletonu [voiture1]. Singleton ten jest zależny od singletonu [personne2], który nie istnieje. W związku z tym ten ostatni zostaje utworzony, a jego metoda [init] zostaje wykonana.
  • wiersz 4: singleton [voiture1] może teraz zostać utworzony. Następnie wykonywana jest jego metoda [init].
  • wiersz 5: metoda [demo2] wyświetla wartość singletonu [voiture1]
  • wiersze 6–8: wykonywana jest metoda [TearDown] z testu. Singletony są niszczone, co powoduje wykonanie ich metod [destroy]

5.3. Przykład 3

Wprowadzamy następującą nową klasę [GroupePersonnes]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class GroupePersonnes
         ' pola prywatne
        Private _membres() As Personne
        Private _groupesDeTravail As Hashtable

         ' właściwości publiczne
        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

         ' konstruktor domyślny
        Public Sub New()

        End Sub

         ' ciąg identyfikacyjny obiektu GroupeDePersonnes
        Public Overrides Function tostring() As String
             ' przeglądanie listy członków grupy
            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=("
             ' przeglądanie słownika grup roboczych
            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
             ' zwracamy wynik
            Return identité + "]"
        End Function

         ' metody 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

Jej dwaj członkowie prywatni to:

_membres: tablica zawierająca listę członków grupy

_groupesDeTravail: słownik przypisujący daną osobę do grupy roboczej

Te elementy prywatne są udostępniane za pośrednictwem właściwości publicznych. Celem tego przykładu jest pokazanie, w jaki sposób Spring umożliwia inicjalizację złożonych obiektów, takich jak obiekty posiadające pola typu tablica lub słownik.

Plik konfiguracyjny Springa o nazwie [spring-config-3.xml] będzie wyglądał następująco:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- osób -->
    <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>
     <!-- samochód -->
    <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>
     <!-- grupa osób -->
    <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. tag <list> pozwala zainicjować pole typu tablicowego lub typu IList różnymi wartościami.
  1. tag <dictionary> pozwala zrobić to samo z polem implementującym interfejs IDictionary

W naszych testach wykorzystamy następującą klasę testową 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
         ' obiekt testowy
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' tworzymy instancję fabryki
            factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
             ' log
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' niszczymy singletony
            factory.Dispose()
             ' monitorowanie
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
    Public Sub demo3()
             ' pobieranie singletonu [groupe1]
            Dim groupe1 As GroupePersonnes = CType(factory.GetObject("groupe1"), GroupePersonnes)
            Console.WriteLine("groupe1={0}", groupe1)
        End Sub

    End Class
End Namespace

Metoda [demo3] pobiera singleton [groupe1] i wyświetla go.

Struktura projektu Visual Studio pozostaje taka sama jak poprzednio, z wyjątkiem tego, że zawiera dwie dodatkowe klasy oraz jeden dodatkowy plik konfiguracyjny.

Image

Wywołanie metody [demo3] w teście NUnit daje następujące wyniki:

Image

Komentarze:

***** 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
  • wiersz 2: metoda atrybutu [Setup] wykonywana przed każdym testem, w tym przypadku [demo]
  • wiersze 3–4: Spring rozpoczyna tworzenie singletonu [groupe1]. Singleton ten jest zależny od singletonów [personne1, personne2], które nie istnieją. Singletony te są tworzone, a ich metody [init] są wykonywane.
  • wiersz 5: singleton [groupe1] może teraz zostać utworzony. Następnie wykonywana jest jego metoda [init].
  • wiersz 6: metoda [demo3] wyświetla wartość singletonu [groupe1]
  • wiersze 7–10: wykonywana jest metoda [TearDown] z testu. Singletony są niszczone, co powoduje wykonanie ich metod [destroy]