2. Konfiguracja aplikacji przy użyciu Spring
Rozważmy klasyczną aplikację trójwarstwową:
![]() |
Załóżmy, że dostęp do warstwy DAO jest kontrolowany przez interfejs [IArticlesDao]:
....
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' lista wszystkich artykułów
Function getAllArticles() As IList
' dodaje artykuł
Function ajouteArticle(ByVal unArticle As Article) As Integer
' usuwa artykuł
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modyfikuje pozycję
Function modifieArticle(ByVal unArticle As Article) As Integer
' wyszukuje artykuł
Function getArticleById(ByVal idArticle As Integer) As Article
' usuwa wszystkie artykuły
Sub clearAllArticles()
' dodaje artykuły do transakcji
Sub doInsertionsInTransaction(ByVal articles As Article())
' zmienia stan magazynowy artykułu
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
W warstwie dostępu do danych, czyli warstwie DAO (Data Access Object), często pracuje się z obiektem SGBD. Rozważmy przypadek, w którym dostęp do niego uzyskuje się za pośrednictwem sterownika ODBC. Szkielet klasy zapewniającej dostęp do tego źródła ODBC mógłby wyglądać następująco:
NameSpace istia.st.articles.dao
Imports System.Data.Odbc
...
Public Class ArticlesDaoPlainODBC
Implements istia.st.articles.dao.IArticlesDao
' pola prywatne
Private connexion As OdbcConnection = Nothing
Private DSN As String
Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)
'pobiera nazwę źródła ODBC
Me.DSN = DSN
' tworzy się ciąg połączenia
Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
'– instancjonowanie połączenia
connexion = New OdbcConnection(connectString)
End Sub
....
End Class
End NameSpace
Aby wykonać operację na źródle ODBC, każda metoda wymaga obiektu [OdbcConnection], który reprezentuje połączenie z bazą danych, przez które będą przebiegać wymiany danych między bazą a aplikacją. Aby utworzyć ten obiekt, potrzebne są trzy informacje:
nazwa źródła ODBC | |
identyfikator, pod którym tworzone jest połączenie | |
hasło powiązane z tym identyfikatorem |
Nasza klasa [ArticlesDaoPlainODBC] uzyskuje te informacje za pośrednictwem agenta zewnętrznego, który instancjonuje element tej klasy. Można zadać sobie pytanie, w jaki sposób agent ten uzyskuje trzy informacje niezbędne do instancjonowania klasy [ArticlesDaoPlainODBC]. Weźmy przykład. Załóżmy, że chcemy napisać klasę testową warstwy [Dao]. Mielibyśmy następującą architekturę:
![]() |
Szkielet klasy testowej Nunit [http://www.nunit.org/] mógłby wyglądać następująco:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
<TestFixture()> _
Public Class NunitTestArticlesDaoPlainOdbc
' obiekt do testowania
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' tworzymy instancję obiektu testowego
articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
End Sub
<Test()> _
Public Sub testGetAllArticles()
' kontrola wizualna
listArticles()
End Sub
' zrzut ekranu
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
Środowisko testowe Nunit stanowi przeniesienie środowiska JUnit, istniejącego dla platformy Java, na platformę .NET. W powyższej klasie metoda posiadająca atrybut <SetUp()> jest wykonywana przed każdą metodą testową. Natomiast metoda posiadająca atrybut <TearDown()> jest wykonywana po każdym teście. W powyższym przykładzie nie ma takiej metody. Widać tutaj, że metoda [init], posiadająca atrybut <SetUp()>, tworzy instancję obiektu [ArticlesDaoPlainODBC], przekazując mu „na stałe” trzy informacje potrzebne konstruktorowi tego obiektu.
Nasza klasa testowa jest narażona na skutki zmiany którejkolwiek z tych „sztywno” zakodowanych informacji. Lepiej byłoby, gdyby informacje te były zapisane w pliku konfiguracyjnym, aby uniknąć niepotrzebnych rekompilacji w przypadku ich zmiany. Typowym rozwiązaniem przy konfigurowaniu aplikacji jest użycie pliku, w którym znajdują się wszystkie informacje, które mogą ulec zmianie w przyszłości. Istnieje wiele różnych rodzajów plików konfiguracyjnych. Obecnie dominuje tendencja do stosowania plików XML. Takie rozwiązanie wybrał Spring. Plik konfigurujący obiekt [ArticlesDaoPlainODBC] mógłby 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>
<!-- klasa implementująca interfejs IArticlesDao -->
<description>Gestion d'une table d'articles</description>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
<constructor-arg index="0">
<value>odbc-firebird-articles</value>
</constructor-arg>
<constructor-arg index="1">
<value>SYSDBA</value>
</constructor-arg>
<constructor-arg index="2">
<value>masterkey</value>
</constructor-arg>
</object>
</objects>
Plik konfiguracyjny Springa opisuje obiekty, które mają zostać zainicjowane. Z reguły nie określa on, w którym momencie zostaną one zainicjowane. O momencie ich zainicjowania decyduje zatem kod, który korzysta z tego pliku. Obiekty opisane w pliku konfiguracyjnym Springa mogą być zainicjowane i zainicjowane na dwa różne sposoby:
- poprzez wskazanie, jak powyżej, parametrów, które mają zostać przekazane do konstruktora obiektu
- poprzez nadanie wartości właściwościom obiektu (Property). W tym przypadku obiekt musi posiadać konstruktor domyślny, którego Spring użyje do instancjonowania.
Obiekty, których rolą w aplikacji jest świadczenie usługi, są często tworzone w jednym egzemplarzu. Nazywa się je singletonami. Tak więc w naszym przykładzie aplikacji wielowarstwowej przedstawionym na początku tego dokumentu dostęp do bazy artykułów będzie zapewniony przez jeden egzemplarz klasy [ArticlesDaoPlainODBC]. W przypadku aplikacji internetowej obiekty usługowe obsługują wielu klientów jednocześnie. Nie tworzy się osobnego obiektu usługowego dla każdego klienta.
Powyższy plik konfiguracyjny Springa pozwala na utworzenie pojedynczego obiektu usługowego typu [ArticlesDaoPlainODBC] w pakiecie o nazwie [istia.st.articles.dao]. Trzy informacje niezbędne dla konstruktora tego obiektu są zdefiniowane wewnątrz tagu <object>...</object>. Liczba takich tagów <object> będzie równa liczbie singletonów do utworzenia.
Przyjrzyjmy się szczegółowo tej konfiguracji:
<objects> jest tagiem nadrzędnym pliku konfiguracyjnego Spring. Oznacza on opis obiektów typu singleton, które mają zostać zainicjowane.
Tag <description> jest opcjonalny. Można go wykorzystać na przykład do opisania roli pliku konfiguracyjnego.
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
...
</object>
Tag <object> służy do opisania obiektu, który ma zostać zainicjowany. Posiada on tutaj dwa atrybuty:
- name: identyfikator obiektu. To właśnie za pomocą tej nazwy kod zewnętrzny będzie odwoływał się do obiektu.
- class: w postaci „nazwa klasy, nazwa zestawu”. Pierwsza informacja to pełna nazwa klasy, która ma zostać zainicjowana. Druga to nazwa pliku DLL, który zawiera tę klasę. W naszym przykładzie klasa znajduje się w pliku o nazwie [articlesdao.dll]
Zawartość tagu <object> służy do opisania sposobu instancjonowania obiektu:
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
<constructor-arg index="0">
<value>odbc-firebird-articles</value>
</constructor-arg>
<constructor-arg index="1">
<value>SYSDBA</value>
</constructor-arg>
<constructor-arg index="2">
<value>masterkey</value>
</constructor-arg>
</object>
Przypomnijmy sygnaturę producenta klasy [ArticlesDaoPlainODBC]:
Obiekt Spring [articlesdao] zostanie zainicjowany przez powyższy konstruktor przy użyciu trzech informacji zawartych w pliku konfiguracyjnym: [odbc-firebird-articles, SYSDBA, masterkey].
W którym momencie nastąpi tworzenie obiektów zdefiniowanych w pliku Spring? W każdej aplikacji znajduje się metoda, która z pewnością zostanie wykonana jako pierwsza. Zazwyczaj to właśnie w niej wywoływane jest tworzenie singletonów. Inicjalizacja aplikacji może zostać powierzona metodzie main tej samej aplikacji, o ile taka istnieje. W przypadku aplikacji ASP.NET może to być metoda [Application_Start] z pliku [global.asax]. W przypadku naszej klasy testowej [Nunit] inicjalizacja aplikacji odbywa się w metodzie powiązanej z atrybutem <Setup()>.
Jak wykorzystać powyższy plik konfiguracyjny w naszej klasie [Nunit]? Oto przykład:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
<TestFixture()> _
Public Class NunitSpringTestArticlesDaoPlainOdbc
' obiekt testowy
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' pobieramy instancję generatora obiektów Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
' żądamy instancjonowania obiektu DAO „articles”
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' weryfikacja wizualna
listArticles()
End Sub
' zrzut ekranu
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
Uwagi:
- aby utworzyć instancje obiektów z pliku konfiguracyjnego Springa, wykorzystuje się obiekt typu [XmlObjectFactory]. Jest to obiekt typu „Factory”, c.a.d – obiekt służący do tworzenia innych obiektów (Factory = fabryka). Spring dysponuje kilkoma typami „Factory” w zależności od używanego pliku konfiguracyjnego. W tym przypadku jest to plik XML, dlatego stosuje się typ [XmlObjectFactory].
- Zgodnie z logiką obiekt typu [XmlObjectFactory] wymaga nazwy pliku konfiguracyjnego XML, w tym przypadku [spring-config-plainodbc.xml]. Dokładniej rzecz biorąc, typ [XmlObjectFactory] jest instancjonowany z wykorzystaniem strumienia odczytu utworzonego na podstawie pliku XML, którego nazwę podajemy.
- Po utworzeniu obiektu typu [XmlObjectFactory] obiekt z pliku konfiguracyjnego jest pobierany przez [XmlObjectFactory].getObject("identyfikator"), gdzie „identyfikator” jest atrybutem [id] jednego z obiektów z pliku konfiguracyjnego.
- Jeśli żądany obiekt nie został jeszcze zainicjowany, Spring inicjuje go na podstawie informacji zawartych w pliku konfiguracyjnym i zwraca odwołanie do niego programowi wywołującemu. Jeśli obiekt został już zainicjowany, Spring po prostu zwraca odwołanie do już istniejącego obiektu. Jest to zasada działania singletonu.
- Warto zauważyć, że klasa testowa [Nunit] nie zna nazwy klasy dostępu do danych. Nazwa ta znajduje się w pliku konfiguracyjnym. Klasa testowa ogranicza się do żądania obiektu implementującego interfejs [IArticlesDao]:
' obiekt do testowania
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
...
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
Na tym właśnie polega zaleta Springa. Jeśli zmienimy klasę implementacyjną, nie będzie trzeba modyfikować naszej klasy testowej. Wystarczy po prostu zmienić plik konfiguracyjny Springa. Klasa testowa z kolei po prostu współpracuje z interfejsem, a nie z klasą.
Zakończmy tę prezentację kilkoma praktycznymi wskazówkami.
Kiedy piszemy „Spring utworzy instancję...”, co dokładnie mamy na myśli? W przypadku platformy .NET Spring znajduje się w trzech plikach:

W przypadku projektu .NET utworzonego w programie Visual Studio, który ma korzystać ze Springa, należy postępować w następujący sposób:
- trzy powyższe pliki należy umieścić w folderze [bin] projektu
- Plik [Spring.Core.dll] musi znaleźć się wśród odwołań projektu:

- klasy korzystające z biblioteki Spring będą musiały importować pewne przestrzenie nazw, w tym często następującą:
Kolejna kwestia praktyczna: gdzie umieścić plik konfiguracyjny Springa? Istnieje kilka możliwych lokalizacji. Jedną z nich jest folder projektu [bin]. To właśnie tam umieszczono plik [spring-config-plainodbc.xml] z omawianego przykładu.

