2. Artykuł 1 – Spring IoC
Cele niniejszego dokumentu:
- zapoznanie się z możliwościami konfiguracji i integracji frameworka Spring (http://www.springframework.org)
- zdefiniowanie i wykorzystanie pojęcia IoC (Inversion of Control), zwanego również wstrzykiwaniem zależności (Dependency Injection)
2.1. Skonfigurowanie aplikacji trójwarstwowej przy użyciu Spring
Rozważmy klasyczną aplikację trójwarstwową:
![]() |
Załóżmy, że dostęp do warstwy biznesowej i DAO jest kontrolowany przez interfejsy Java:
- interfejs [IArticlesDao] dla warstwy dostępu do danych
- interfejs [IArticlesManager] dla warstwy biznesowej
W warstwie dostępu do danych lub warstwie DAO (Data Access Object) często pracuje się z obiektem SGBD, a tym samym z sterownikiem JDBC. Rozważmy szkielet klasy uzyskującej dostęp do tabeli artykułów w SGBD:
Aby wykonać operację na pliku SGBD, każda metoda wymaga obiektu [Connection], który reprezentuje połączenie z bazą danych, przez które będą przebiegać wymiany danych między bazą a kodem Java. Aby utworzyć ten obiekt, potrzebne są cztery informacje:
nazwa klasy sterownika JDBC dla SGBD | |
adres URL JDBC bazy, z której ma być korzystano | |
identyfikator, pod którym tworzone jest połączenie | |
hasło dla tego identyfikatora |
W jaki sposób nasza poprzednia klasa [ArticlesDaoPlainJdbc] może uzyskać te informacje? Istnieje kilka możliwości:
rozwiązanie 1 – informacje są zakodowane na stałe w klasie:
Wadą tego rozwiązania jest konieczność modyfikacji kodu Java za każdym razem, gdy nastąpi zmiana tych informacji, na przykład zmiana hasła.
rozwiązanie 2 – informacje są przekazywane do obiektu podczas jego tworzenia:
W tym przypadku obiekt otrzymuje podczas tworzenia informacje niezbędne do działania. Problem przenosi się zatem na kod, który przekazał mu te cztery informacje. W jaki sposób je uzyskał? Następująca klasa [ArticlesManagerWithDataBase] z warstwy biznesowej mogłaby utworzyć obiekt [ArticlesDaoPlainJdbc] z warstwy dostępu do danych:
![]() |
Widać, że również w tym przypadku informacje niezbędne do utworzenia obiektu [ArticlesDaoPlainJdbc] są przekazywane konstruktorowi obiektu [ArticlesManagerWithDataBase]. Można sobie wyobrazić, że są one przekazywane przez wyższą warstwę, taką jak warstwa interfejsu użytkownika. W ten sposób, krok po kroku, dochodzimy do najwyższej warstwy aplikacji. Ze względu na swoje położenie warstwa ta nie jest wywoływana przez żadną warstwę, która mogłaby przekazać jej potrzebne informacje konfiguracyjne. Należy zatem znaleźć inne rozwiązanie niż konfiguracja za pomocą konstruktora. Typowym rozwiązaniem służącym do konfiguracji aplikacji na poziomie jej najwyższej warstwy jest wykorzystanie pliku, w którym znajdują się wszystkie informacje mogące ulec zmianie w czasie. Plików takich może być kilka. Podczas uruchamiania aplikacji warstwa inicjalizacyjna utworzy wówczas wszystkie lub część obiektów niezbędnych dla poszczególnych warstw aplikacji.
Istnieje szeroka gama plików konfiguracyjnych. Obecnie dominuje tendencja do stosowania plików XML. Takie rozwiązanie wybrał Spring. Plik konfigurujący obiekt [ArticlesDaoPlainJdbc] mógłby wyglądać następująco:
Aplikacja to zbiór obiektów, które Spring nazywa „beanami”, ponieważ są one zgodne ze standardem JavaBean dotyczącym nazewnictwa metod dostępu (getterów/setterów) do pól prywatnych obiektu. 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, którą tutaj omawiamy, dostęp do bazy artykułów będzie zapewniony przez jedyny egzemplarz klasy [ArticlesDaoPlainJdbc]. 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 [ArticlesDaoPlainJdbc] w pakiecie o nazwie [istia.st.articles.dao]. Cztery informacje niezbędne dla konstruktora tego obiektu są zdefiniowane wewnątrz tagu <bean>...</bean>. Liczba takich tagów <bean> będzie równa liczbie singletonów do utworzenia.
W którym momencie nastąpi tworzenie obiektów zdefiniowanych w pliku Spring? Inicjalizacja aplikacji może zostać powierzona metodzie main tej samej aplikacji, o ile taka istnieje. W przypadku aplikacji internetowej może to być metoda [init] głównego serwletu. W każdej aplikacji znajduje się metoda, która z pewnością zostanie wykonana jako pierwsza. Zazwyczaj to właśnie w niej odbywa się tworzenie singletonów.
Weźmy przykład. Załóżmy, że chcemy przetestować poprzednią klasę [ArticlesDaoPlainJdbc] za pomocą testu JUnit. Klasa testowa JUnit posiada metodę [setUp], która jest wykonywana przed wszystkimi innymi metodami. To właśnie tam utworzymy singleton [ArticlesDaoPlainJdbc].
Jeśli zastosujemy rozwiązanie polegające na przekazywaniu informacji konfiguracyjnych za pomocą konstruktora, otrzymamy następującą klasę testową:
Klasa wywołująca [TestArticlesPlainJdbc] musi znać cztery informacje niezbędne do zainicjowania singletonu [ArticlesDaoPlainJdbc], który ma zostać utworzony.
Jeśli zastosujemy rozwiązanie polegające na przekazywaniu informacji konfiguracyjnych za pomocą pliku konfiguracyjnego, moglibyśmy uzyskać następującą klasę testową, korzystając z opisanego powyżej pliku Spring.
W tym przypadku klasa wywołująca [TestSpringArticlesPlainJdbc] nie musi znać informacji niezbędnych do zainicjowania tworzonego singletonu. Musi jedynie znać:
- [springArticlesPlainJdbc.xml]: nazwę opisanego powyżej pliku konfiguracyjnego Spring
- [articlesDao]: nazwę singletonu, który ma zostać utworzony
Zmiana w pliku konfiguracyjnym, poza tymi dwoma elementami, nie ma żadnego wpływu na kod Java. Ta metoda konfiguracji obiektów aplikacji jest bardzo elastyczna. Aby się skonfigurować, aplikacja musi znać tylko dwie rzeczy:
- nazwę pliku Spring zawierającego definicje singletonów, które mają zostać utworzone
- nazwy tych singletonów, które służą kodowi Java do uzyskania odwołania do obiektów, z którymi zostały powiązane za pomocą pliku konfiguracyjnego
2.2. Wstrzykiwanie zależności i odwrócenie kontroli
Wprowadźmy teraz pojęcie wstrzykiwania zależności (Dependency Injection) stosowanego przez Spring do konfiguracji aplikacji. Używa się również terminu odwrócenie kontroli (IoC, Inversion of Control). Rozważmy utworzenie singletonu [ArticlesManagerWithDataBase] w warstwie biznesowej naszej aplikacji:
![]() |
Aby uzyskać dostęp do danych singletonu SGBD, warstwa biznesowa musi korzystać z usług obiektu implementującego interfejs [IArticlesDao], na przykład obiektu typu [ArticlesDaoPlainJdbc]. Kod klasy [ArticlesManagerWithDataBase] mógłby wyglądać następująco:
public class ArticlesManagerWithDataBase implements IArticlesManager {
// instancja modułu dostępu do danych
private IArticlesDao articlesDao;
....
public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
...
// utworzenie usługi dostępu do danych
articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
...
}
public ... doSomething(...){
...
}
}
Klasa [ArticlesDaoPlainJdbc] ma tutaj implementować interfejs [IArticlesDao]:
Aby utworzyć singleton typu [IArticlesDao] niezbędny do działania klasy, jej konstruktor wyraźnie wykorzystuje nazwę klasy implementującej interfejs [IArticlesDao]:
W kodzie występuje zatem twarda zależność od nazwy klasy. Gdyby zmieniła się klasa implementująca interfejs [IArticlesDao], kod powyższego konstruktora musiałby zostać zmodyfikowany. Pomiędzy obiektami występują następujące relacje:
![]() |
Klasa [ArticlesManagerWithDataBase] sama inicjuje utworzenie obiektu [ArticlesDaoPlainJdbc], którego potrzebuje. Wracając do terminu „odwrócenie kontroli”, można powiedzieć, że to ona ma „kontrolę” nad utworzeniem obiektu, którego potrzebuje.
Gdybyśmy mieli napisać klasę testową JUnit dla klasy [ArticlesManagerWithDataBase], mogłaby ona wyglądać mniej więcej tak:
Klasa testowa tworzy instancję klasy biznesowej [ArticlesManagerWithDataBase], która z kolei w swoim konstruktorze tworzy instancję klasy dostępu do danych [ArticlesDaoPlainJdbc].
Rozwiązanie oparte na Springiem wyeliminuje konieczność, aby klasa biznesowa [ArticlesManagerWithDataBase] znała nazwę [ArticlesDaoPlainJdbc] klasy dostępu do danych, której potrzebuje. Umożliwi to zmianę tej nazwy bez ingerencji w kod Java klasy biznesowej. Spring umożliwi jednoczesne utworzenie dwóch singletonów: dla warstwy dostępu do danych oraz dla warstwy biznesowej. Plik konfiguracyjny Springa zdefiniuje nową fasolkę:
Nowością jest bean definiujący singleton klasy biznesowej, który ma zostać utworzony:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
- zdefiniowano klasę implementującą bean [articlesManager]: [ArticlesManagerWithDataBase]
- pole [articlesDao] fasoli otrzymuje wartość za pomocą tagu <property name="articlesDao">. Jest to pole zdefiniowane w klasie [ArticlesManagerWithDataBase]:
Aby pole [articlesDao] mogło zostać zainicjowane przez Springa za pomocą tagu <property>, pole to musi być zgodne ze standardem JavaBean oraz musi istnieć metoda [setArticlesDao] służąca do inicjalizacji pola [articlesDao]. Należy zwrócić uwagę na nazwę metody, która jest ściśle wyprowadzona z nazwy pola. Równolegle często istnieje metoda [get...] służąca do pobrania wartości pola. W tym przypadku jest to metoda [getArticlesDao]. W tej nowej wersji klasa [ArticlesManagerWithDataBase] nie posiada już konstruktora. Nie jest on już potrzebny.
- Wartość, która zostanie przypisana do pola [articlesDao] przez Spring, pochodzi z bean’a [articlesDao] zdefiniowanego w jego pliku konfiguracyjnym:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
<bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
<constructor-arg index="0">
.............
</bean>
- gdy Spring utworzy singleton [ArticlesManagerWithDataBase], będzie musiał również utworzyć singleton [ArticlesDaoPlainJdbc]:
- Spring utworzy graf zależności beanów i stwierdzi, że bean [articlesManager] zależy od beana [articlesDao]
- utworzy bean [articlesDao], a więc obiekt typu [ArticlesDaoPlainJdbc]
- następnie utworzy bean [articlesManager] typu [ArticlesManagerWithDataBase]
Wyobraźmy sobie teraz test JUnit dla klasy [ArticlesManagerWithDataBase]. Mógłby on wyglądać następująco:
Prześledźmy proces tworzenia dwóch singletonów zdefiniowanych w pliku Spring o nazwie [springArticlesManagerWithDataBase.xml].
- Powyższa metoda [setUp] żąda odwołania do bean o nazwie [articlesManager]
- Spring sprawdza swój plik konfiguracyjny i znajduje bean o nazwie [articlesManager]. Jeśli jest on już utworzony, zwraca po prostu odwołanie do obiektu (singletonu), w przeciwnym razie tworzy go.
- Spring wykrywa zależność beana [articlesManager] od beana [articlesDao]. W związku z tym tworzy singleton [articlesDao] typu [ArticlesDaoPlainJdbc], jeśli ten jeszcze nie został utworzony (singleton).
- Tworzy singleton [articlesManager] typu [ArticlesManagerWithDataBase]
Mechanizm ten można przedstawić schematycznie w następujący sposób:
![]() |
Przypomnijmy szkielet klasy [ArticlesManagerWithDataBase]:
Po zakończeniu tworzenia singletonów przez Spring otrzymujemy obiekt typu [ArticlesManagerWithDataBase], którego pole [articlesDao] zostało zainicjowane w sposób dla niego nieznany. Mówi się, że do obiektu [ArticlesManagerWithDataBase] wstrzyknięto zależność. Mówi się również, że nastąpiło odwrócenie kontroli: to już nie obiekt [ArticlesManagerWithDataBase] podejmuje inicjatywę samodzielnego tworzenia obiektu implementującego interfejs [IArticlesDao], którego potrzebuje, ale to aplikacja na najwyższym poziomie (podczas inicjalizacji) dba o utworzenie wszystkich potrzebnych jej obiektów, zarządzając wzajemnymi zależnościami między nimi.
Główną zaletą konfiguracji singletonu [ArticlesManagerWithDataBase] za pomocą pliku Spring jest to, że teraz można zmienić klasę implementującą odpowiadającą polu [articlesDao] w klasie [ArticlesManagerWithDataBase] bez konieczności modyfikowania kodu tej klasy. Wystarczy zmienić nazwę klasy w definicji beana [articlesDao] w pliku Spring:
stanie się na przykład:
Bean [ArticlesManagerWithDataBase] będzie współpracował z tą nową klasą dostępu do danych, nawet o tym nie wiedząc.
2.3. Spring IoC w praktyce
2.3.1. Przykład 1
Rozważmy następującą klasę:
Klasa ta zawiera:
- dwa pola prywatne: „nazwa” i „wiek”
- metody odczytu (get) i zapisu (set) tych dwóch pól
- 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:
Plik ten będzie nosił nazwę config.xml.
- Definiuje on dwa bean'y o kluczach „osoba1” i „osoba2” typu [Personne]
- inicjuje pola [nom, age] dla 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 jedną klasę testową JUnit, do której będziemy sukcesywnie dodawać metody. Pierwsza wersja tej klasy będzie wyglądać następująco:
Uwagi:
- aby uzyskać komponenty zdefiniowane w pliku [config.xml], używamy obiektu typu [ListableBeanFactory]. Istnieją inne typy obiektów umożliwiające dostęp do beanów. Obiekt [ListableBeanFactory] jest pobierany w metodzie [setUp] klasy testowej i zapisywany w zmiennej prywatnej. Dzięki temu będzie on dostępny dla wszystkich metod testowych.
- Plik [config.xml] zostanie umieszczony w katalogu [ClassPath] aplikacji, c.a.d, w jednym z katalogów przeszukiwanych przez maszynę wirtualną Java podczas wyszukiwania klasy, do której odwołuje się aplikacja. Obiekt [ClassPathResource] służy do wyszukiwania zasobu w pliku [ClassPath] aplikacji, w tym przypadku w pliku [config.xml].
- Spring może korzystać z plików konfiguracyjnych w różnych formatach. Obiekt [XmlBeanFactory] umożliwia analizę pliku konfiguracyjnego w formacie XML.
- Przetworzenie pliku Springa zwraca obiekt typu [ListableBeanFactory], w tym przypadku obiekt bf. Za pomocą tego obiektu można uzyskać bean oznaczony kluczem C poprzez wyrażenie bf.getBean(C).
- Metoda [test1] pobiera i wyświetla wartości beanów o kluczach „personne1” i „personne2”.
Struktura projektu Eclipse naszej aplikacji wygląda następująco:

Uwagi:
- folder [src] zawiera kod źródłowy. Skompilowany kod trafi do folderu [bin], który nie jest tutaj przedstawiony.
- Plik [config.xml] znajduje się w katalogu głównym folderu [src]. Podczas kompilacji projektu plik ten jest automatycznie kopiowany do folderu [bin], który stanowi część folderu aplikacji [ClassPath]. To właśnie tam jest on wyszukiwany przez obiekt [ClassPathResource].
- Folder [lib] zawiera trzy biblioteki Java niezbędne dla aplikacji:
- commons-logging.jar i spring-core.jar dla klas Spring
- junit.jar dla klas JUnit
- folder [lib] również stanowi część pliku [ClassPath] aplikacji
Wywołanie metody [test1] w teście JUnit daje następujące wyniki:
Uwagi:
- Spring rejestruje pewną liczbę zdarzeń za pomocą biblioteki [commons-logging.jar]. Te logi pozwalają nam lepiej zrozumieć działanie Springa.
- Plik [config.xml] został załadowany, a następnie wykorzystany
- operacja*
spowodowała utworzenie beana [personne1]. Widoczny jest odpowiedni wpis w dzienniku Springa. Ponieważ w definicji beana [personne1] wpisano [init-method="init"], wykonano metodę [init] utworzonego obiektu [Personne]. Wyświetlany jest odpowiedni komunikat.
- Operacja
spowodowała wyświetlenie wartości utworzonego obiektu [Personne].
- To samo zjawisko powtarza się w przypadku fasoli klucza [personne2].
- Ostatnia operacja
personne2 = (Personne) bf.getBean("personne2");
System.out.println("personne2=" + personne2.toString());
nie spowodowała 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.
- Można zauważyć, że nie ma żadnego śladu metody [close] obiektu [Personne], mimo że zapisaliśmy ją w definicji beanów [destroy-method=close]. Możliwe, że metoda ta jest wykonywana dopiero wtedy, gdy pamięć zajmowana przez obiekt zostanie zwolniona przez moduł czyszczący pamięć (garbage collector). W momencie, gdy to nastąpi, aplikacja jest już zakończona, a zapis na ekranie nie ma żadnego efektu. Należy to sprawdzić.
Ponieważ opanowaliśmy już podstawy konfiguracji Springa, nasze wyjaśnienia będą odtąd nieco szybsze.
2.3.2. Przykład 2
Rozważmy następującą nową klasę [Voiture]:
Klasa zawiera:
- trzy pola prywatne: typ, marka i właściciel. Pola te można inicjować i odczytywać za pomocą publicznych metod get i set klasy. Można je również zainicjować za pomocą konstruktora Voiture(String, String, Personne). Klasa posiada również konstruktor bez argumentów, aby spełniać normę JavaBean.
- 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ę close, która zostanie wywołana podczas niszczenia obiektu
Aby utworzyć obiekty typu [Voiture], wykorzystamy następujący plik Spring [config.xml]:
Plik ten dodaje do poprzednich definicji bean o kluczu „voiture1” typu [Voiture]. Aby zainicjować ten bean, można by napisać:
Zamiast skorzystać z tej metody, którą już przedstawiliśmy, zdecydowaliśmy się w tym przypadku użyć konstruktora Voiture(String, String, Personne) tej klasy. Ponadto bean [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 już przedstawioną klasę testową JUnit, dodając do niej następującą metodę [test2]:
Metoda [test2] pobiera bean [voiture1] i wyświetla go.
Struktura projektu Eclipse pozostaje taka sama jak w poprzednim teście. Wywołanie metody [test2] w teście JUnit daje następujące wyniki:
Uwagi:
- metoda [test2] żąda odwołania do bean’a [voiture1]
- wiersz 4: Spring rozpoczyna tworzenie bean’a [voiture1], ponieważ ten bean nie został jeszcze utworzony (singleton)
- wiersz 6: ponieważ bean [voiture1] odwołuje się do beana [personne2], ten ostatni bean jest z kolei tworzony
- wiersz 7: bean [personne2] został utworzony. Następnie wykonywana jest jego metoda [init].
- wiersz 9: Spring informuje, że użyje konstruktora do utworzenia bean’a [voiture1]
- wiersz 10: utworzono bean [voiture1]. Następnie wykonywana jest jego metoda [init].
- wiersz 11: metoda [test2] wyświetla wartość fasoli [voiture1]
2.3.3. Przykład 3
Wprowadzamy następującą nową klasę [GroupePersonnes]:
Jej dwa elementy prywatne to:
elementy: tablica osób należących do grupy
groupesDeTravail: słownik przypisujący osobę do grupy roboczej
Warto tutaj zauważyć, że klasa [GroupePersonnes] nie definiuje konstruktora bez argumentów, aby zachować zgodność ze standardem JavaBean. Przypominamy, że w przypadku braku jakiegokolwiek konstruktora istnieje konstruktor „domyślny”, który jest konstruktorem bez argumentów i nie wykonuje żadnej operacji.
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. Do poprzedniego pliku Spring [config.xml] dodajemy nową fasolkę:
- tag <list> pozwala zainicjować pole typu tablica lub implementujące interfejs List z różnymi wartościami.
- tag <map> pozwala zrobić to samo z polem implementującym interfejs Map
Do naszych testów wykorzystamy już przedstawioną klasę testową JUnit, dodając do niej następującą metodę [test3]:
Metoda [test3] pobiera bean [groupe1] i wyświetla go.
Struktura projektu Eclipse pozostaje taka sama jak w poprzednim teście. Wywołanie metody [test3] w teście JUnit daje następujące wyniki:
Uwagi:
- metoda [test3] wymaga odwołania do bean’a [groupe1]
- wiersz 4: Spring rozpoczyna tworzenie tego beana
- ponieważ bean [groupe1] odwołuje się do beanów [personne1] i [personne2], oba te beany są tworzone (wiersze 6 i 9), a ich metoda init jest wykonywana (wiersze 7 i 10)
- wiersz 11: utworzono bean [groupe1]. Teraz wykonywana jest jego metoda [init].
- wiersz 12: wywołano wyświetlanie za pomocą metody [test3].
2.4. Spring do konfiguracji trójwarstwowych aplikacji internetowych
2.4.1. Ogólna architektura aplikacji
Chcemy zbudować aplikację trójwarstwową o następującej strukturze:
![]() |
- trzy warstwy zostaną oddzielone dzięki zastosowaniu interfejsów Java
- integracja trzech warstw zostanie zrealizowana za pomocą Springa
- dla każdej z trzech warstw utworzymy oddzielne pakiety o nazwach Control, Domain i Dao. Dodatkowy pakiet będzie zawierał aplikacje testowe.
Struktura aplikacji w środowisku Eclipse mogłaby wyglądać następująco:

2.4.2. Warstwa DAO odpowiedzialna za dostęp do danych
Warstwa DAO zaimplementuje następujący interfejs:
package istia.st.demo.dao;
public interface IDao1 {
public int doSometingInDaoLayer(int a, int b);
}
- Należy napisać dwie klasy Dao1Impl1 i Dao1Impl2 implementujące interfejs IDao1. Metoda Dao1Impl1. doSomethingInDaoLayer zwróci a+b, a metoda Dao1Impl2. doSomethingInDaoLayer zwróci a-b.
- Napisz klasę testową JUnit, która przetestuje dwie poprzednie klasy
2.4.3. Warstwa biznesowa
Warstwa biznesowa będzie implementować następujący interfejs:
package istia.st.demo.domain;
public interface IDomain1 {
public int doSomethingInDomainLayer(int a, int b);
}
- Należy napisać dwie klasy Domain1Impl1 i Domain1Impl2 implementujące interfejs IDomain1. Klasy te będą miały konstruktor przyjmujący parametr typu IDao1. Metoda Domain1Impl1.doSomethingInDomainLayer zwiększy wartości a i b o jedność, a następnie przekaże te dwa parametry do metody doSomethingInDaoLayer obiektu typu IDao1, który została przekazana. Z kolei metoda Domain1Impl2.doSomethingInDomainLayer zmniejszy wartości a i b o jeden, a następnie wykona tę samą operację.
- napisać klasę testową JUnit sprawdzającą obie poprzednie klasy
2.4.4. Warstwa interfejsu użytkownika
Warstwa interfejsu użytkownika zaimplementuje następujący interfejs:
package istia.st.demo.control;
public interface IControl1 {
public int doSometingInControlLayer(int a, int b);
}
- Należy napisać dwie klasy Control1Impl1 i Control1Impl2 implementujące interfejs IControl1. Klasy te będą miały konstruktor przyjmujący parametr typu IDomain1. Metoda Control1Impl1.doSomethingInControlLayer zwiększy wartości a i b o jedność, a następnie przekaże te dwa parametry do metody doSomethingInDomainLayer obiektu typu IDomain1, który została przekazana. Z kolei metoda Control11Impl2.doSomethingInControlLayer zmniejszy wartości a i b o jeden, a następnie wykona tę samą operację.
- napisać klasę testową JUnit sprawdzającą obie poprzednie klasy
2.4.5. Integracja ze Springiem
- Napisz plik konfiguracyjny Springa, który określi, z jakich klas będzie korzystać każda z trzech poprzednich warstw
- Napisz klasę testową JUnit wykorzystującą różne konfiguracje Springa, aby podkreślić elastyczność napisanej aplikacji
- napisz samodzielną aplikację (metoda main), przekazującą dwa parametry do interfejsu IControl1 i wyświetlającą wynik zwrócony przez interfejs.
2.4.6. Rozwiązanie
2.4.6.1. Projekt Eclipse

Archiwa z folderu [lib] zostały dodane do folderu [ClassPath] w ramach projektu.
2.4.6.2. Pakiet [istia.st.demo.dao]
Interfejs:
Pierwsza klasa implementacji:
Druga klasa implementacji:
2.4.6.3. Pakiet [istia.st.demo.domain]
Interfejs:
Pierwsza klasa implementacyjna:
Druga klasa implementacji:
2.4.6.4. Pakiet [istia.st.demo.control]
Interfejs
Pierwsza klasa implementacyjna:
Druga klasa implementacji:
2.4.6.5. Pliki konfiguracyjne [Spring]
Pierwszy plik [springMainTest1.xml]:
Drugi plik [springMainTest2.xml]:
2.4.6.6. Pakiet testowy [istia.st.demo.tests]
Test typu [main]:
Wyniki uzyskane w konsoli Eclipse:
Kolejny test wykorzystujący drugi plik konfiguracyjny [Spring]:
Wyniki uzyskane w konsoli Eclipse:
Na koniec test Junit:
2.5. Conclusion
Framework Spring zapewnia prawdziwą elastyczność zarówno w zakresie architektury aplikacji, jak i ich konfiguracji. Wykorzystaliśmy koncepcję IoC, jeden z dwóch filarów Springa. Drugim filarem jest AOP (programowanie zorientowane aspektowo), którego nie omówiliśmy. Pozwala ono, poprzez konfigurację, dodawać „zachowanie” do metody klasy bez modyfikowania jej kodu. W uproszczeniu AOP umożliwia filtrowanie wywołań określonych metod:
![]() |
- Filtr może być uruchomiony przed lub po docelowej metodzie M, albo w obu przypadkach.
- Metoda M ignoruje istnienie tych filtrów. Są one definiowane w pliku konfiguracyjnym Springa.
- Kod metody M nie ulega zmianie. Filtry są klasami Java, które należy skonstruować. Spring udostępnia predefiniowane filtry, w szczególności do obsługi transakcji SGBD.
- Filtry są komponentami typu bean i jako takie są zdefiniowane w pliku konfiguracyjnym Springa jako komponenty typu bean.
Typowym filtrem jest filtr transakcyjny. Weźmy metodę M z warstwy biznesowej, realizującą dwie nierozerwalnie powiązane operacje na danych (jednostka pracy). Wykorzystuje ona dwie metody M1 i M2 z warstwy DAO w celu wykonania tych dwóch operacji.
![]() |
Ponieważ metoda M znajduje się w warstwie biznesowej, nie zajmuje się nośnikiem tych danych. Nie musi na przykład zakładać, że dane znajdują się w SGBD i że musi umieścić dwa wywołania metod M1 oraz M2 w ramach transakcji SGBD. To warstwa DAO powinna zająć się tymi szczegółami. Rozwiązaniem powyższego problemu jest zatem utworzenie metody w warstwie DAO, która sama wywoływałaby metody M1 i M2, które to wywołania obejmowałaby w transakcji SGBD.
![]() |
Rozwiązanie oparte na filtrowaniu AOP jest bardziej elastyczne. Umożliwia ono zdefiniowanie filtra, który przed wywołaniem funkcji M rozpocznie transakcję, a po jej zakończeniu wykona commit lub rollback w zależności od sytuacji.
![]() |
Takie podejście ma kilka zalet:
- po zdefiniowaniu filtra można go zastosować do wielu metod, na przykład do wszystkich, które wymagają transakcji
- metody objęte filtrowaniem nie wymagają przepisywania
- filtry, które mają być używane, są definiowane w konfiguracji, więc można je zmieniać
Oprócz koncepcji IoC i AOP, Spring oferuje wiele klas wspierających aplikacje trójwarstwowe:
- dla JDBC – SqlMap (iBatis), dla Hibernate – JDO (Java Data Object) w warstwie DAO
- dla modelu MVC w warstwie interfejsu użytkownika
Więcej informacji: http://www.springframework.org.









