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:

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

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

Wykonanie metody [demo] w teście NUnit daje wyniki wskazane powyżej i przedstawione poniżej:
Uwagi:
- wiersz 2: test rozpoczyna się od wykonania metody atrybutu <Setup()>
- wiersz 3: operacja
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.
- wiersz 4: operacja
spowodowała wyświetlenie wartości utworzonego obiektu [Personne].
- wiersze 5–6: to samo zjawisko powtarza się w przypadku fasoli klucza [personne2].
- wiersz 7: ostatnia operacja
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
dała tylko jedną linię wyświetlania:
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
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:
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:

Wykonanie testu NUnit daje następujące wyniki:

Omówmy wyświetlane ekrany:
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>
- tag <list> pozwala zainicjować pole typu tablicowego lub typu IList różnymi wartościami.
- 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.

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

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