15. Spring IoC
15.1. Introduction
Zamierzamy zapoznać się z możliwościami konfiguracji i integracji frameworka Spring (http://www.springframework.org), a także zdefiniować i wykorzystać pojęcie IoC (Inversion of Control), zwane również wstrzykiwaniem zależności (Dependency Injection)
Rozważmy właśnie zbudowaną przez nas aplikację trójwarstwową:
![]() |
Aby odpowiedzieć na żądania użytkownika, kontroler [Application] musi zwrócić się do warstwy [service]. W naszym przykładzie była to instancja typu [DaoImpl]. Kontroler [Application] uzyskał odwołanie do warstwy [service] w swojej metodzie [init] (punkt 14.8.3):
- wiersz 6: warstwa [dao] została zainicjowana poprzez jawne utworzenie instancji [DaoImpl]
- wiersz 9: warstwa [service] została zainicjowana poprzez jawne utworzenie instancji [ServiceImpl]
Przypomnijmy, że klasy [DaoImpl] i [ServiceImpl] implementują odpowiednio interfejsy [IDao] i [IService]. W przyszłych wersjach interfejs [IDao] zostanie zaimplementowany przez klasę zarządzającą listą osób przechowywaną w bazie danych. Dla przykładu nazwijmy tę klasę [DaoBD]. Zastąpienie implementacji [DaoImpl] warstwy [dao] implementacją [DaoBD] będzie wymagało ponownej kompilacji warstwy [web]. W rzeczywistości wiersz 6 powyżej, który instancjonuje warstwę [dao] z typem [DaoImpl], musi teraz instancjonować ją z typem [DaoBD]. Nasza warstwa [web] jest zatem zależna od warstwy [dao]. Wiersz 9 powyżej pokazuje, że jest ona również zależna od warstwy [service].
Spring IoC pozwoli nam stworzyć aplikację trójwarstwową, w której warstwy są od siebie niezależne, c.a.d, co oznacza, że zmiana jednej z nich nie wymaga zmiany pozostałych. Zapewnia to dużą elastyczność w rozwoju aplikacji.
Poprzednia architektura ulegnie następującym zmianom:
![]() |
Dzięki [Spring IoC] kontroler [Application] uzyska potrzebne mu odwołanie do warstwy [service] w następujący sposób:
- w swojej metodzie [init] zwróci się do warstwy [Spring IoC] o przekazanie mu odniesienia do warstwy [service]
- [Spring IoC] wykorzysta wówczas plik konfiguracyjny XML, który wskazuje mu, która klasa ma zostać zainicjowana oraz w jaki sposób ma zostać zainicjowana.
- [Spring IoC] zwraca kontrolerowi [Application] odwołanie do utworzonej warstwy [service].
Zaletą tego rozwiązania jest to, że nazwy klas instancjonujących poszczególne warstwy nie są już zakodowane na stałe w metodzie [init] kontrolera, lecz znajdują się po prostu w pliku konfiguracyjnym. Zmiana implementacji warstwy spowoduje zmianę w tym pliku konfiguracyjnym, ale nie w kontrolerze.
Przedstawmy teraz możliwości [Spring IoC] na podstawie przykładów.
15.2. Spring IoC w praktyce
15.2.1. Spring
[Spring IoC] stanowi część większego projektu dostępnego pod adresem [http://www.springframework.org/] (maj 2006):
![]() | ![]() |
![]() |
- [1]: Spring wykorzystuje różne technologie stron trzecich, określone tutaj jako dépendances. Należy pobrać wersję zawierającą dépendances, aby uniknąć konieczności późniejszego pobierania bibliotek narzędzi stron trzecich.
- [2]: struktura katalogów pobranego pliku ZIP
- [3]: dystrybucja [Spring], c.a.d. Archiwa .jar samego projektu Spring bez jego zależności. Funkcjonalność [IoC] projektu Spring jest zapewniona przez archiwa [spring-core.jar, spring-beans.jar].
- [4,5]: archiwa narzędzi innych producentów
15.2.2. Projekty Eclipse z przykładami
Stworzymy trzy przykłady ilustrujące wykorzystanie Springa IoC. Wszystkie będą znajdować się w następującym projekcie Eclipse:

Projekt [springioc-exemples] jest skonfigurowany tak, aby pliki źródłowe i skompilowane klasy znajdowały się w katalogu głównym projektu:
![]() |
- [1]: struktura katalogów projektu [Eclipse]
- [2]: pliki konfiguracyjne Springa znajdują się w katalogu głównym projektu, a więc w katalogu Classpath aplikacji
- [3]: klasy z przykładu 1
- [4]: klasy z przykładu 2
- [5]: klasy z przykładu 3
- [6]: biblioteki projektu [spring-core.jar, spring-beans.jar] znajdują się w folderze [dist] dystrybucji Spring, a [commons-logging.jar] w folderze [lib/jakarta-commons]. Te trzy archiwa zostały dołączone do pakietu aplikacji Classpath.
15.2.3. Przykład 1
Elementy z przykładu 1 zostały umieszczone w pakiecie [springioc01] projektu:
![]()
Klasa [Personne] ma następującą postać:
Klasa zawiera:
- wiersze 6–7: dwa pola prywatne „nom” i „age”
- wiersze 23–38: metody odczytu (get) i zapisu (set) tych dwóch pól
- wiersze 10–12: metodę toString służącą do pobrania wartości obiektu [Personne] w postaci ciągu znaków
- wiersze 15–21: metoda init, która zostanie wywołana przez Spring podczas tworzenia obiektu, oraz metoda close, która zostanie wywołana podczas niszczenia obiektu
Aby utworzyć instancje obiektów typu [Personne] za pomocą Springa, wykorzystamy następujący plik [spring-config-01.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="personne1" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
<property name="nom" value="Simon" />
<property name="age" value="40" />
</bean>
<bean id="personne2" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
<property name="nom" value="Brigitte" />
<property name="age" value="20" />
</bean>
</beans>
- wiersze 3, 12: tag <beans> jest tagiem nadrzędnym plików konfiguracyjnych Springa. Wewnątrz tego tagu tag <bean> służy do definiowania różnych obiektów, które mają zostać utworzone.
- wiersze 4–7: definicja beana
- wiersz 4: bean nosi nazwę [personne1] (atrybut id) i jest instancją klasy [istia.st.springioc01.Personne] (atrybut class). Metoda [init] tej instancji zostanie wywołana po jej utworzeniu (atrybut init-method), a metoda [close] tej instancji zostanie wywołana przed jej zniszczeniem (atrybut destroy-method).
- wiersz 5: definiują wartość, jaką należy przypisać właściwości [nom] (atrybut name) utworzonej instancji [Personne]. Aby przeprowadzić tę inicjalizację, Spring wykorzysta metodę [setNom]. Metoda ta musi więc istnieć. Tak jest w tym przypadku.
- wiersz 6: to samo dotyczy właściwości [age].
- wiersze 8–11: analogiczna definicja bean o nazwie [personne2]
Klasa testowa [Main] wygląda następująco:
Komentarze:
- wiersz 10: aby uzyskać bean'y zdefiniowane w pliku [spring-config-01.xml], używamy obiektu typu [XmlBeanFactory], który umożliwia instancjonowanie bean'ów zdefiniowanych w pliku XML. Plik [spring-config-01.xml] zostanie umieszczony w pliku [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 [spring-config-01.xml]. Uzyskany obiekt [bf] (Bean Factory) pozwala uzyskać odwołanie do bean o nazwie „XX” za pomocą instrukcji bf.getBean("XX").
- wiersz 12: żądamy odwołania do bean o nazwie [personne1] w pliku [spring-config-01.xml].
- wiersz 13: wyświetlana jest wartość odpowiadającego obiektu [Personne].
- wiersze 15–16: wykonuje się to samo dla bean o nazwie [personne2].
- wiersze 18–19: ponownie wywołuje się bean o nazwie [personne2].
- wiersz 21: usuwa się wszystkie bean o nazwach [bf] i c.a.d, a także te utworzone na podstawie pliku [spring-config-01.xml].
Wykonanie klasy [Main] daje następujące wyniki:
Uwagi:
- wiersz 1 uzyskano poprzez wykonanie wiersza 12 pliku [Main]. Operacja
wymusiła utworzenie bean’a [personne1]. Ponieważ w definicji bean’a [personne1] wpisano [init-method="init"], wykonano metodę [init] utworzonego obiektu [Personne]. Wyświetlany jest odpowiedni komunikat.
- wiersz 2: wiersz 13 obiektu [Main] spowodował wyświetlenie wartości utworzonego obiektu [Personne].
- wiersze 3–4: to samo zjawisko powtarza się w przypadku fasoli o nazwie [personne2].
- wiersz 5: operacja z wierszy 18–19 obiektu [Main]
personne2 = (Personne) bf.getBean("personne2");
System.out.println("personne2=" + personne2.toString());
nie spowodowała utworzenia nowego obiektu typu [Personne]. Gdyby tak się stało, wyświetlono by metodę [init]. Na tym polega zasada singletonu. Spring domyślnie tworzy tylko jeden egzemplarz beanów z pliku konfiguracyjnego. Jest to usługa referencji do obiektów. Jeśli poprosimy ją o referencję do obiektu, który nie został jeszcze utworzony, tworzy go i zwraca referencję. Jeśli obiekt został już utworzony, Spring po prostu zwraca referencję do niego. W tym przypadku, ponieważ [personne2] został już utworzony, Spring po prostu zwraca referencję do niego.
- Wyświetlenia w wierszach 6–7 zostały wywołane przez wiersz 21 pliku [Main], który żąda zniszczenia wszystkich beanów, do których odwołuje się obiekt [XmlBeanFactory bf], a więc beanów [personne1, personne2]. Ponieważ oba te bean'y posiadają atrybut [destroy-method="close"], wykonywana jest metoda [close] obu bean'ów, co powoduje wyświetlenie wierszy 6–7.
Ponieważ opanowaliśmy już podstawy konfiguracji Springa, nasze wyjaśnienia będą odtąd nieco szybsze.
15.2.4. Przykład 2
Elementy przykładu 2 znajdują się w pakiecie [springioc02] projektu:

Pakiet [springioc02] uzyskuje się najpierw poprzez skopiowanie i wklejenie pakietu [springioc01], a następnie dodaje się do niego klasę [Voiture] i dostosowuje klasę [Main] do nowego przykładu.
Klasa [Voiture] ma następującą postać:
Klasa zawiera:
- wiersze 5–7: trzy pola prywatne typu, marki i właściciela. Pola te mogą być inicjowane i odczytywane za pomocą publicznych metod get i set klasy w wierszach 26–48. Można je również zainicjować za pomocą konstruktora Voiture(String, String, Personne) zdefiniowanego w wierszach 13–17. Klasa posiada również konstruktor bez argumentów, aby spełniać normę JavaBean.
- wiersze 20–23: metoda toString służąca do pobrania wartości obiektu [Voiture] w postaci ciągu znaków
- wiersze 51–57: metoda init, która zostanie wywołana przez Springa zaraz po utworzeniu obiektu, oraz metoda close, która zostanie wywołana podczas niszczenia obiektu
Aby utworzyć obiekty typu [Voiture], wykorzystamy następujący plik Spring [spring-config-02.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="personne1" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
<property name="nom" value="Simon" />
<property name="age" value="40" />
</bean>
<bean id="personne2" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
<property name="nom" value="Brigitte" />
<property name="age" value="20" />
</bean>
<bean id="voiture1" class="istia.st.springioc02.Voiture" init-method="init" destroy-method="close">
<constructor-arg index="0" value="Peugeot" />
<constructor-arg index="1" value="307" />
<constructor-arg index="2">
<ref local="personne2" />
</constructor-arg>
</bean>
</beans>
Plik ten dodaje do fasoli zdefiniowanych w pliku [spring-config-01.xml] fasolę o kluczu „voiture1” typu [Voiture] (wiersze 12–17). Aby zainicjować tę fasolę, można by napisać:
Zamiast wybierać metodę przedstawioną już w przykładzie 1, zdecydowaliśmy się tutaj użyć konstruktora Voiture(String, String, Personne) tej klasy.
- wiersz 12: definicja nazwy bean, jego klasy, metody do wykonania po jego instancjonowaniu oraz metody do wykonania po jego usunięciu.
- wiersz 13: wartość pierwszego parametru konstruktora [Voiture(String, String, Personne)].
- wiersz 14: wartość drugiego parametru konstruktora [Voiture(String, String, Personne)].
- wiersze 15–17: wartość trzeciego parametru konstruktora [Voiture(String, String, Personne)]. Ten parametr jest typu [Personne]. Jako wartość podaje się odwołanie (tag ref) do bean’a [personne2] zdefiniowanego w tym samym pliku (atrybut local).
W naszych testach wykorzystamy następującą klasę [Main]:
Metoda [main] pobiera odwołanie do komponentu [voiture1] (wiersz 12) i wyświetla je (wiersz 13). Wyniki są następujące:
Uwagi:
- metoda [main] żąda odwołania do bean'a [voiture1] (wiersz 12). Spring rozpoczyna tworzenie bean'a [voiture1], ponieważ ten bean nie został jeszcze utworzony (singleton). Ponieważ bean [voiture1] odwołuje się do beana [personne2], ten ostatni bean jest z kolei tworzony. Utworzono bean [personne2]. Następnie wykonywana jest jego metoda [init] (wiersz 1) wyników. Następnie instancjonowany jest bean [voiture1]. Następnie wykonywana jest jego metoda [init] (wiersz 2) wyników.
- Wiersz 3 wyników pochodzi z wiersza 13 kodu [main]: wyświetlana jest wartość fasoli [voiture1].
- Wiersz 15 klasy [main] żąda zniszczenia wszystkich istniejących beanów, co powoduje wyświetlenie wierszy 4 i 5 wyników.
15.2.5. Przykład 3
Elementy z przykładu 3 znajdują się w pakiecie [springioc03] projektu:

Pakiet [springioc03] uzyskuje się najpierw poprzez skopiowanie i wklejenie pakietu [springioc01], a następnie dodaje się do niego klasę [GroupePersonnes], usuwa się klasę [Voiture] i dostosowuje klasę [Main] do nowego przykładu.
Klasa [GroupePersonnes] ma następującą postać:
Jej dwa elementy prywatne to:
wiersz 8: członkowie: tablica osób należących do grupy
wiersz 9: groupesDeTravail: słownik przypisujący osobę do grupy roboczej
Warto tutaj zauważyć, że klasa [GroupePersonnes] nie definiuje konstruktora bez argumentów. 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. Plik beanów [spring-config-03.xml] z przykładu 3 wygląda następująco:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="personne1" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
<property name="nom" value="Simon" />
<property name="age" value="40" />
</bean>
<bean id="personne2" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
<property name="nom" value="Brigitte" />
<property name="age" value="20" />
</bean>
<bean id="groupe1" class="istia.st.springioc03.GroupePersonnes" init-method="init" destroy-method="close">
<property name="membres">
<list>
<ref local="personne1" />
<ref local="personne2" />
</list>
</property>
<property name="groupesDeTravail">
<map>
<entry key="Brigitte" value="Marketing" />
<entry key="Simon" value="Ressources humaines" />
</map>
</property>
</bean>
</beans>
- wiersze 14–17: tag <list> pozwala zainicjować pole typu tablica lub implementujące interfejs List z różnymi wartościami.
- wiersze 20–23: tag <map> pozwala wykonać to samo z polem implementującym interfejs Map.
Do naszych testów wykorzystamy następującą klasę [Main]:
- wiersze 12–13: zwracamy się do Springa o odwołanie do beana [groupe1] i wyświetlamy jego wartość.
Otrzymane wyniki są następujące:
Uwagi:
- w wierszu 12 pliku [Main] żądamy odwołania do bean’a [groupe1]. Spring rozpoczyna tworzenie tego beana. Ponieważ bean [groupe1] odwołuje się do beanów [personne1] i [personne2], oba te beany są tworzone (wiersze 1 i 2 wyników). Następnie tworzona jest instancja fasoli [groupe1] i wykonywana jest jej metoda [init] (wiersz 3 wyników).
- Wiersz 13 klasy [Main] powoduje wyświetlenie wiersza 4 wyników.
- Wiersz 15 klasy [Main] powoduje wyświetlenie wierszy 5–7 wyników.
15.3. Konfiguracja aplikacji wielowarstwowej przy użyciu Spring
Rozważmy aplikację trójwarstwową o następującej strukturze:
użytkownik
Dane
Warstwa biznesowa [metier]
Warstwa dostępu do danych [dao]
Warstwa interfejsu użytkownika [ui]
W niniejszym artykule zamierzamy wykazać zalety wykorzystania Springa do budowy takiej architektury.
- Trzy warstwy zostaną uniezależnione dzięki zastosowaniu interfejsów Java
- Integracja tych trzech warstw zostanie zrealizowana przez Spring
Struktura aplikacji w środowisku Eclipse mogłaby wyglądać następująco:
![]() |
- [1]: warstwa [dao]:
- [IDao]: interfejs warstwy
- [Dao1, Dao2]: dwie implementacje tego interfejsu
- [2]: warstwa [metier]:
- [IMetier]: interfejs warstwy
- [Metier1, Metier2]: dwie implementacje tego interfejsu
- [3]: warstwa [ui]:
- [IUi]: interfejs warstwy
- [Ui1, Ui2]: dwie implementacje tego interfejsu
- [4]: pliki konfiguracyjne Spring dla aplikacji. Skonfigurujemy aplikację na dwa sposoby.
- [5]: biblioteki niezbędne do działania aplikacji. Są to te same biblioteki, które wykorzystano w poprzednich przykładach.
- [6]: pakiet testów. [Main1] będzie korzystał z konfiguracji [spring-config-01.xml], a [Main2] z konfiguracji [spring-config-02.xml].
Celem tego przykładu jest pokazanie, że możemy zmienić implementację jednej lub kilku warstw aplikacji bez żadnego wpływu na pozostałe warstwy. Wszystko odbywa się w pliku konfiguracyjnym Springa.
Warstwa [dao]
Warstwa [dao] implementuje następujący interfejs [IDao]:
Implementacja [Dao1] będzie wyglądać następująco:
Implementacja [Dao2] będzie wyglądać następująco:
Warstwa [métier]
Warstwa [métier] implementuje następujący interfejs [IMetier]:
Implementacja [Metier1] będzie wyglądać następująco:
Implementacja [Metier2] będzie wyglądać następująco:
Warstwa [ui]
Warstwa [ui] implementuje następujący interfejs [IUi]:
Implementacja [Ui1] będzie wyglądać następująco:
Implementacja [Ui2] będzie wyglądać następująco:
Pliki konfiguracyjne Spring
Pierwszy plik [spring-config-01.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- klasa dao -->
<bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
<!-- klasa biznesowa -->
<bean id="metier" class="istia.st.springioc.troistier.metier.Metier1">
<property name="dao">
<ref local="dao" />
</property>
</bean>
<!-- klasa UI -->
<bean id="ui" class="istia.st.springioc.troistier.ui.Ui1">
<property name="metier">
<ref local="metier" />
</property>
</bean>
</beans>
Drugi plik [spring-config-02.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- klasa DAO -->
<bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
<!-- klasa biznesowa -->
<bean id="metier"
class="istia.st.springioc.troistier.metier.Metier2">
<property name="dao">
<ref local="dao" />
</property>
</bean>
<!-- klasa UI -->
<bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
<property name="metier">
<ref local="metier" />
</property>
</bean>
</beans>
Programy testowe
Program [Main1] wygląda następująco:
Program [Main1] korzysta z pliku konfiguracyjnego [spring-config-01.xml], a tym samym z implementacji warstw zawartych w pliku [Ui1, Metier1, Dao1]. Wyniki uzyskane w konsoli Eclipse:
Program [Main2] wygląda następująco:
Program [Main2] korzysta z pliku konfiguracyjnego [spring-config-02.xml], a tym samym z implementacji warstw [Ui2, Metier2, Dao2]. Wyniki uzyskane w konsoli Eclipse:
15.4. Conclusion
Stworzona przez nas aplikacja charakteryzuje się dużą elastycznością rozbudowy. Zmianę implementacji warstwy można wprowadzić poprzez prostą konfigurację. Kod pozostałych warstw pozostaje niezmieniony. Jest to możliwe dzięki koncepcji IoC, która stanowi jeden z dwóch filarów Springa. Drugim filarem jest AOP (programowanie zorientowane na aspekty), którego nie omówiliśmy. Pozwala ono na dodawanie, również poprzez konfigurację, „zachowania” do metody klasy bez modyfikowania jej kodu.






