15. Spring IoC
15.1. Introduction
Ми пропонуємо ознайомитися з можливостями налаштування та інтеграції фреймворку Spring (http://www.springframework.org), а також визначити та застосувати поняття IoC (Inversion of Control), яке також називають ін’єкцією залежностей (Dependency Injection)
Розглянемо трирівневий додаток, який ми щойно створили:
![]() |
Щоб відповісти на запити користувача, контролер [Application] повинен звернутися до шару [service]. У нашому прикладі це був екземпляр типу [DaoImpl]. контролер [Application] отримав посилання на рівень [service] у своєму методі [init] (параграф 14.8.3):
- рядок 6: шар [dao] було інстанційовано шляхом явного створення екземпляра [DaoImpl]
- рядок 9: шар [service] було інстанційовано шляхом явного створення екземпляра [ServiceImpl]
Нагадаємо, що класи [DaoImpl] та [ServiceImpl] реалізують інтерфейси, відповідно [IDao] та [IService]. У майбутніх версіях інтерфейс [IDao] буде реалізовано класом, що керує списком осіб, розміщеним у базі даних. Для прикладу назвемо цей клас [DaoBD]. Заміна реалізації [DaoImpl] шару [dao) на реалізацію [DaoBD] вимагатиме перекомпіляції шару [web]. Адже у рядку 6 вище, який створює екземпляр шару [dao] з типом [DaoImpl], тепер потрібно створити екземпляр з типом [DaoBD]. Отже, наш шар [web] залежить від шару [dao]. Рядок 9 вище показує, що він також залежить від шару [service].
Spring IoC дозволить нам створити трирівневу систему, в якій рівні є незалежними один від одного, c.a.d, тобто зміна одного рівня не вимагає зміни інших. Це забезпечує велику гнучкість у розвитку системи.
Попередня архітектура буде розвиватися наступним чином:
![]() |
Завдяки [Spring IoC] контролер [Application] отримає необхідне йому посилання на рівень [service] таким чином:
- у своєму методі [init] він звернеться до рівня [Spring IoC] із запитом надати йому посилання на рівень [service]
- [Spring IoC] потім використає файл конфігурації XML, який вказує, який клас має бути інстанційований і як його слід ініціалізувати.
- [Spring IoC] повертає контролеру [Application] посилання на створений шар [service].
Перевага цього рішення полягає в тому, що відтепер імена класів, які створюють екземпляри різних шарів, більше не задаються жорстко в методі [init] контролера, а просто містяться у файлі конфігурації. Зміна реалізації шару призведе до зміни в цьому файлі конфігурації, але не в контролері.
Тепер розглянемо можливості [Spring IoC] на прикладах.
15.2. Spring IoC на практиці
15.2.1. Spring
[Spring IoC] є частиною більш масштабного проєкту, доступного за посиланням [http://www.springframework.org/] (травень 2006 року):
![]() | ![]() |
![]() |
- [1]: Spring використовує різні сторонні технології, які тут називаються dépendances. Необхідно завантажити версію з dépendances, щоб уникнути необхідності подальшого завантаження бібліотек сторонніх інструментів.
- [2]: структура заархівованого файлу, що завантажується
- [3]: дистрибутив [Spring], c.a.d — архівні файли .jar самого проєкту Spring без його залежностей. Функціональність [IoC] проекту Spring забезпечується архівами [spring-core.jar, spring-beans.jar].
- [4,5]: архіви сторонніх інструментів
15.2.2. Проєкти Eclipse для прикладів
Ми створимо три приклади, що ілюструють використання Spring IoC. Усі вони будуть розміщені в наступному проекті Eclipse:

Проєкт [springioc-exemples] налаштований таким чином, що вихідні файли та скомпільовані класи знаходяться в кореневій папці проєкту:
![]() |
- [1]: структура папок проєкту [Eclipse]
- [2]: файли конфігурації Spring знаходяться в кореневій папці проекту, тобто в Classpath додатка
- [3]: класи з прикладу 1
- [4]: класи з прикладу 2
- [5]: класи з прикладу 3
- [6]: бібліотеки проекту [spring-core.jar, spring-beans.jar] знаходяться у папці [dist] дистрибутива Spring, а [commons-logging.jar] — у папці [lib/jakarta-commons]. Ці три архіви були включені до Classpath додатка.
15.2.3. Приклад 1
Елементи прикладу 1 розміщено в пакеті [springioc01] цього проєкту:
![]()
Клас [Personne] має такий вигляд:
У класі містяться:
- рядки 6–7: два приватні поля nom та age
- рядки 23–38: методи читання (get) та запису (set) цих двох полів
- рядки 10–12: метод toString для отримання значення об’єкта [Personne] у вигляді символьного рядка
- рядки 15–21: метод init, який буде викликаний Spring під час створення об’єкта, та метод close, який буде викликаний під час знищення об’єкта
Щоб створити екземпляри об’єктів типу [Personne] за допомогою Spring, ми використаємо такий файл [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>
- рядки 3, 12: тег <beans> є кореневим тегом конфігураційних файлів Spring. Усередині цього тегу тег <bean> використовується для визначення різних об’єктів, які потрібно створити.
- рядки 4–7: визначення біна
- рядок 4: bean має назву [personne1] (атрибут id) і є екземпляром класу [istia.st.springioc01.Personne] (атрибут class). Метод [init] екземпляра буде викликано після його створення (атрибут init-method), а метод [close] екземпляра буде викликано перед його знищенням (атрибут destroy-method).
- рядок 5: визначають значення, яке слід присвоїти властивості [nom] (атрибут name) створеного екземпляра [Personne]. Для виконання цієї ініціалізації Spring використовуватиме метод [setNom]. Отже, цей метод має існувати. У даному випадку це так.
- рядок 6: те саме стосується властивості [age].
- рядки 8–11: аналогічне визначення біна з іменем [personne2]
Тестовий клас [Main] має такий вигляд:
Коментарі:
- рядок 10: щоб отримати біни, визначені у файлі [spring-config-01.xml], ми використовуємо об’єкт типу [XmlBeanFactory], який дозволяє створити екземпляри бінів, визначених у файлі XML. Файл [spring-config-01.xml] буде розміщений у [ClassPath] додатка c.a.d в одному з каталогів, які переглядає віртуальна машина Java під час пошуку класу, на який посилається додаток. Об’єкт [ClassPathResource] використовується для пошуку ресурсу у файлі [ClassPath] додатка, в даному випадку — у файлі [spring-config-01.xml]. Отриманий об’єкт [bf] (Bean Factory) дозволяє отримати посилання на бін із назвою «XX» за допомогою інструкції bf.getBean("XX").
- рядок 12: запитується посилання на бін з іменем [personne1] у файлі [spring-config-01.xml].
- рядок 13: виводиться значення відповідного об’єкта [Personne].
- рядки 15–16: те саме робиться для біна з іменем [personne2].
- рядки 18–19: повторно запитується бін із назвою [personne2].
- рядок 21: видаляються всі біни [bf] та c.a.d, створені на основі файлу [spring-config-01.xml].
Виконання класу [Main] дає такі результати:
Коментарі:
- рядок 1 було отримано в результаті виконання рядка 12 класу [Main]. Операція
призвела до створення біна [personne1]. Оскільки у визначенні біна [personne1] було вказано [init-method="init"], було виконано метод [init] створеного об’єкта [Personne]. Відображається відповідне повідомлення.
- рядок 2: рядок 13 у [Main] вивів значення створеного об’єкта [Personne].
- рядки 3–4: те саме повторюється для біна з назвою [personne2].
- рядок 5: операція з рядків 18–19 об’єкта [Main]
personne2 = (Personne) bf.getBean("personne2");
System.out.println("personne2=" + personne2.toString());
не призвела до створення нового об’єкта типу [Personne]. Якби це сталося, ми б побачили виклик методу [init]. Це принцип синглтона. Spring за замовчуванням створює лише один екземпляр бінів зі свого файлу конфігурації. Це служба посилань на об’єкти. Якщо їй подати запит на посилання на об’єкт, який ще не створено, вона створює його та повертає посилання на нього. Якщо об’єкт уже створено, Spring просто повертає посилання на нього. У даному випадку, оскільки [personne2] вже було створено, Spring просто повертає посилання на нього.
- Виведення рядків 6–7 було викликано рядком 21 у [Main], який вимагає знищення всіх бінів, на які посилається об’єкт [XmlBeanFactory bf], тобто бінів [personne1, personne2]. Оскільки ці два біни мають атрибут [destroy-method="close"], метод [close] обох бінів виконується і викликає виведення рядків 6–7.
Оскільки ми вже ознайомилися з основами конфігурації Spring, відтепер наші пояснення будуть дещо швидшими.
15.2.4. Приклад 2
Елементи прикладу 2 розміщені в пакеті [springioc02] проекту:

Пакет [springioc02] спочатку створюється шляхом копіювання та вставлення пакета [springioc01], після чого до нього додається клас [Voiture], а клас [Main] адаптується до нового прикладу.
Клас [Voiture] має такий вигляд:
Клас містить:
- рядки 5–7: три приватні поля type, marque та propriétaire. Ці поля можна ініціалізувати та зчитувати за допомогою публічних методів get та set бінів у рядках 26–48. Їх також можна ініціалізувати за допомогою конструктора Voiture(String, String, Personne), визначеного в рядках 13–17. Клас також має конструктор без аргументів, щоб відповідати стандарту JavaBean.
- рядки 20–23: метод toString для отримання значення об’єкта [Voiture] у вигляді символьного рядка
- рядки 51–57: метод init, який буде викликаний Spring одразу після створення об’єкта, та метод close, який буде викликаний під час знищення об’єкта
Для створення об’єктів типу [Voiture] ми використаємо такий файл 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>
Цей файл додає до бінів, визначених у [spring-config-01.xml], бін із ключем «voiture1» типу [Voiture] (рядки 12–17). Щоб ініціалізувати цей бін, можна було б написати:
Замість того, щоб вибрати цей метод, який вже було представлено у прикладі 1, ми вирішили використати тут конструктор Voiture(String, String, Personne) цього класу.
- рядок 12: визначення імені біна, його класу, методу, що має виконуватися після його інстанціювання, та методу, що має виконуватися після його видалення.
- рядок 13: значення першого параметра конструктора [Voiture(String, String, Personne)].
- рядок 14: значення другого параметра конструктора [Voiture(String, String, Personne)].
- рядки 15–17: значення третього параметра конструктора [Voiture(String, String, Personne)]. Цей параметр має тип [Personne]. Як значення йому передається посилання (тег ref) на bean [personne2], визначений у тому ж файлі (атрибут local).
Для наших тестів ми будемо використовувати такий клас [Main]:
Метод [main] запитує посилання на bean [voiture1] (рядок 12) і виводить його (рядок 13). Результати такі:
Коментарі:
- метод [main] запитує посилання на бін [voiture1] (рядок 12). Spring починає створення біна [voiture1], оскільки цей бін ще не створено (синглтон). Оскільки бін [voiture1] посилається на бін [personne2], останній, у свою чергу, також створюється. Бін [personne2] було створено. Потім виконується його метод [init] (рядок 1) результатів. Далі створюється екземпляр біна [voiture1]. Потім виконується його метод [init] (рядок 2) результатів.
- Рядок 3 результатів походить із рядка 13 [main]: відображається значення біна [voiture1].
- У рядку 15 [main] вимагається знищення всіх існуючих бінів, що призводить до відображення рядків 4 та 5 результатів.
15.2.5. Приклад 3
Елементи прикладу 3 розміщені в пакеті [springioc03] проекту:

Пакет [springioc03] спочатку отримують шляхом копіювання/вставлення з пакета [springioc01], а потім до нього додають клас [GroupePersonnes], видаляється клас [Voiture], а клас [Main] адаптується до нового прикладу.
Клас [GroupePersonnes] має такий вигляд:
Його два приватні елементи:
рядок 8: members: масив осіб, що входять до групи
рядок 9: groupesDeTravail: словник, що прив’язує особу до робочої групи
Слід зауважити, що клас [GroupePersonnes] не визначає конструктора без аргументів. Нагадаємо, що за відсутності будь-якого конструктора існує конструктор «за замовчуванням», який є конструктором без аргументів і не виконує жодних дій.
Тут ми прагнемо показати, як Spring дозволяє ініціалізувати складні об’єкти, такі як об’єкти, що містять поля типу масиву або словника. Файл бінів [spring-config-03.xml] із прикладу 3 має такий вигляд:
<?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>
- рядки 14–17: тег <list> дозволяє ініціалізувати поле типу масив або таке, що реалізує інтерфейс List, з різними значеннями.
- рядки 20–23: тег <map> дозволяє зробити те саме з полем, що реалізує інтерфейс Map.
Для наших тестів ми будемо використовувати такий клас [Main]:
- рядки 12–13: запитуємо у Spring посилання на bean [groupe1] і виводимо його значення.
Отримано такі результати:
Коментарі:
- у рядку 12 файлу [Main] запитується посилання на бін [groupe1]. Spring починає створення цього біна. Оскільки бін [groupe1] посилається на біни [personne1] та [personne2], ці два біни створюються (рядки 1 та 2 результатів). Потім створюється екземпляр біна [groupe1] і виконується його метод [init] (рядок 3 результатів).
- У рядку 13 [Main] відображається рядок 4 результатів.
- У рядку 15 [Main] відображаються рядки 5–7 результатів.
15.3. Налаштування n-рівневого додатка за допомогою Spring
Розглянемо трирівневий додаток із такою структурою:
користувач
Дані
Бізнес-шар [metier]
Рівень доступу до даних [dao]
Рівень користувацького інтерфейсу [ui]
Тут ми пропонуємо продемонструвати переваги використання Spring для побудови такої архітектури.
- три шари стануть незалежними завдяки використанню інтерфейсів Java
- Інтеграція трьох рівнів буде здійснена за допомогою Spring
Структура додатка в Eclipse може виглядати наступним чином:
![]() |
- [1]: рівень [dao]:
- [IDao]: інтерфейс рівня
- [Dao1, Dao2]: дві реалізації цього інтерфейсу
- [2]: рівень [metier]:
- [IMetier]: інтерфейс шару
- [Metier1, Metier2]: дві реалізації цього інтерфейсу
- [3]: рівень [ui]:
- [IUi]: інтерфейс шару
- [Ui1, Ui2]: дві реалізації цього інтерфейсу
- [4]: файли конфігурації Spring для додатка. Ми налаштуємо додаток двома способами.
- [5]: бібліотеки, необхідні для роботи програми. Це ті самі бібліотеки, що використовувалися в попередніх прикладах.
- [6]: пакет тестів. [Main1] використовуватиме конфігурацію [spring-config-01.xml], а [Main2] — конфігурацію [spring-config-02.xml].
Мета цього прикладу — продемонструвати, що ми можемо змінювати реалізацію одного або декількох рівнів додатка без жодного впливу на інші рівні. Усе відбувається у файлі конфігурації Spring.
Рівень [dao]
Рівень [dao] реалізує такий інтерфейс [IDao]:
Реалізація [Dao1] буде такою:
Реалізація [Dao2] буде такою:
Рівень [métier]
Рівень [métier] реалізує наступний інтерфейс [IMetier]:
Реалізація [Metier1] буде такою:
Реалізація [Metier2] буде такою:
Рівень [ui]
Рівень [ui] реалізує наступний інтерфейс [IUi]:
Реалізація [Ui1] буде такою:
Реалізація [Ui2] буде такою:
Конфігураційні файли Spring
Перший — [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>
<!-- клас dao -->
<bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
<!-- бізнес-клас -->
<bean id="metier" class="istia.st.springioc.troistier.metier.Metier1">
<property name="dao">
<ref local="dao" />
</property>
</bean>
<!-- клас UI -->
<bean id="ui" class="istia.st.springioc.troistier.ui.Ui1">
<property name="metier">
<ref local="metier" />
</property>
</bean>
</beans>
Другий — [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>
<!-- клас DAO -->
<bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
<!-- бізнес-клас -->
<bean id="metier"
class="istia.st.springioc.troistier.metier.Metier2">
<property name="dao">
<ref local="dao" />
</property>
</bean>
<!-- клас UI -->
<bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
<property name="metier">
<ref local="metier" />
</property>
</bean>
</beans>
Тестові програми
Програма [Main1] має такий вигляд:
Програма [Main1] використовує файл конфігурації [spring-config-01.xml] і, отже, реалізацію шарів [Ui1, Metier1, Dao1]. Результати, отримані в консолі Eclipse:
Програма [Main2] має такий вигляд:
Програма [Main2] використовує файл конфігурації [spring-config-02.xml] і, відповідно, реалізації шарів [Ui2, Metier2, Dao2]. Результати, отримані в консолі Eclipse:
15.4. Conclusion
Створений нами додаток має велику гнучкість у розвитку. Реалізацію одного шару можна змінити за допомогою простої конфігурації. Код інших шарів залишається незмінним. Це досягається завдяки концепції IoC, яка є одним із двох стовпів Spring. Іншим стовпом є AOP (аспектно-орієнтоване програмування), яке ми не розглядали. Воно дозволяє, також за допомогою конфігурації, додавати «поведінку» до методу класу, не змінюючи його код.






