Skip to content

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

@SuppressWarnings("serial")
public class Application extends HttpServlet {
...

     // обслуговування
    ServiceImpl service=null;
...
     // ініціалізація
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
         // інстанціювання шару [dao]
        DaoImpl dao = new DaoImpl();
        dao.init();
         // інстанціювання шару [service]
        service = new ServiceImpl();
        service.setDao(dao);
    }
  • рядок 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] таким чином:

  1. у своєму методі [init] він звернеться до рівня [Spring IoC] із запитом надати йому посилання на рівень [service]
  2. [Spring IoC] потім використає файл конфігурації XML, який вказує, який клас має бути інстанційований і як його слід ініціалізувати.
  3. [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:

Image

Проєкт [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] цього проєкту:

Image

Клас [Personne] має такий вигляд:

package istia.st.springioc01;

public class Personne {

     // характеристики
    private String nom;
    private int age;

     // відображення «Особа»
    public String toString() {
        return "nom=[" + this.nom + "], age=[" + this.age + "]";
    }

     // init-close
    public void init() {
        System.out.println("init personne [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy personne [" + this.toString() + "]");
    }

     // гетери-сеттери
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

}

У класі містяться:

  • рядки 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] має такий вигляд:

package istia.st.springioc01;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
         // обробка конфігураційного файлу Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
         // отримання біна [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
         // отримання біна [personne2]
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         // повторне отримання біна [personne2]
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         // видалення всіх бінів
        bf.destroySingletons();
    }
}

Коментарі:

  • рядок 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
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Коментарі:

  • рядок 1 було отримано в результаті виконання рядка 12 класу [Main]. Операція
Personne personne1 = (Personne) bf.getBean("personne1");

призвела до створення біна [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] проекту:

Image

Пакет [springioc02] спочатку створюється шляхом копіювання та вставлення пакета [springioc01], після чого до нього додається клас [Voiture], а клас [Main] адаптується до нового прикладу.

Клас [Voiture] має такий вигляд:

package istia.st.springioc02;

public class Voiture {
     // характеристики
    private String marque;
    private String type;
    private Personne propriétaire;

     // конструктори
    public Voiture() {
    }

    public Voiture(String marque, String type, Personne propriétaire) {
        setMarque(marque);
        setType(type);
        setPropriétaire(propriétaire);
    }

     // toString
    public String toString() {
        return "Voiture : marque=[" + this.marque + "] type=[" + this.type
                + "] propriétaire=[" + this.propriétaire + "]";
    }

     // гетери-сеттери
    public String getMarque() {
        return marque;
    }

    public void setMarque(String marque) {
        this.marque = marque;
    }

    public Personne getPropriétaire() {
        return propriétaire;
    }

    public void setPropriétaire(Personne propriétaire) {
        this.propriétaire = propriétaire;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

     // init-close
    public void init() {
        System.out.println("init voiture [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy voiture [" + this.toString() + "]");
    }
}

Клас містить:

  • рядки 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
2
3
4
5
6
7
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" init-method="init" destroy-method="close">
        <property name="marque" value="Peugeot"/>
        <property name="type" value="307"/>
        <property name="propriétaire">
            <ref local="personne2"/>
        </property>
</bean>

Замість того, щоб вибрати цей метод, який вже було представлено у прикладі 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]:

package istia.st.springioc02;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
         // використання конфігураційного файлу Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
         // отримання біна [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
         // видалення бінів
        bf.destroySingletons();
    }
}

Метод [main] запитує посилання на bean [voiture1] (рядок 12) і виводить його (рядок 13). Результати такі:

1
2
3
4
5
init personne [nom=[Brigitte], age=[20]]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]
destroy voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
destroy personne [nom=[Brigitte], age=[20]]

Коментарі:

  1. метод [main] запитує посилання на бін [voiture1] (рядок 12). Spring починає створення біна [voiture1], оскільки цей бін ще не створено (синглтон). Оскільки бін [voiture1] посилається на бін [personne2], останній, у свою чергу, також створюється. Бін [personne2] було створено. Потім виконується його метод [init] (рядок 1) результатів. Далі створюється екземпляр біна [voiture1]. Потім виконується його метод [init] (рядок 2) результатів.
  2. Рядок 3 результатів походить із рядка 13 [main]: відображається значення біна [voiture1].
  3. У рядку 15 [main] вимагається знищення всіх існуючих бінів, що призводить до відображення рядків 4 та 5 результатів.

15.2.5. Приклад 3

Елементи прикладу 3 розміщені в пакеті [springioc03] проекту:

Image

Пакет [springioc03] спочатку отримують шляхом копіювання/вставлення з пакета [springioc01], а потім до нього додають клас [GroupePersonnes], видаляється клас [Voiture], а клас [Main] адаптується до нового прикладу.

Клас [GroupePersonnes] має такий вигляд:

package istia.st.springioc03;

import java.util.Map;

public class GroupePersonnes {

     // характеристики
    private Personne[] membres;
    private Map groupesDeTravail;

     // геттери — сеттери
    public Personne[] getMembres() {
        return membres;
    }

    public void setMembres(Personne[] membres) {
        this.membres = membres;
    }

    public Map getGroupesDeTravail() {
        return groupesDeTravail;
    }

    public void setGroupesDeTravail(Map groupesDeTravail) {
        this.groupesDeTravail = groupesDeTravail;
    }

     // відображення
    public String toString() {
        String liste = "membres : ";
        for (int i = 0; i < this.membres.length; i++) {
            liste += "[" + this.membres[i].toString() + "]";
        }
        return liste + ", groupes de travail = "
                + this.groupesDeTravail.toString();
    }

     // ініціалізація та закриття
    public void init() {
        System.out.println("init GroupePersonnes [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
    }
}

Його два приватні елементи:

рядок 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]:

package istia.st.springioc03;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
         // обробка конфігураційного файлу Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
         // отримання біна [groupe1]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
         // видалення бінів
        bf.destroySingletons();
    }
}
  • рядки 12–13: запитуємо у Spring посилання на bean [groupe1] і виводимо його значення.

Отримано такі результати:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}
destroy GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Коментарі:

  • у рядку 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]:

1
2
3
4
5
package istia.st.springioc.troistier.dao;

public interface IDao {
    public int doSomethingInDaoLayer(int a, int b);
}

Реалізація [Dao1] буде такою:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }
}

Реалізація [Dao2] буде такою:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }
}

Рівень [métier]


Рівень [métier] реалізує наступний інтерфейс [IMetier]:

1
2
3
4
5
package istia.st.springioc.troistier.metier;

public interface IMetier {
      public int doSomethingInBusinessLayer(int a, int b);
}

Реалізація [Metier1] буде такою:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier1 implements IMetier {

     // шар [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

Реалізація [Metier2] буде такою:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier2 implements IMetier {

     // шар [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a--;
        b--;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

Рівень [ui]


Рівень [ui] реалізує наступний інтерфейс [IUi]:

1
2
3
4
5
package istia.st.springioc.troistier.ui;

public interface IUi {
    public int doSomethingInUiLayer(int a, int b);
}

Реалізація [Ui1] буде такою:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui1 implements IUi {

     // шар [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

Реалізація [Ui2] буде такою:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui2 implements IUi {

     // шар [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b--;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

Конфігураційні файли 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] має такий вигляд:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main1 {

    public static void main(String[] args) {
         // отримуємо реалізацію інтерфейсу IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
         // використовуємо клас
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // виводимо результат
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

Програма [Main1] використовує файл конфігурації [spring-config-01.xml] і, отже, реалізацію шарів [Ui1, Metier1, Dao1]. Результати, отримані в консолі Eclipse:

ui(10,20)=34

Програма [Main2] має такий вигляд:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main2 {

    public static void main(String[] args) {
         // отримуємо реалізацію інтерфейсу IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
         // використовується клас
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // виводимо результат
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

Програма [Main2] використовує файл конфігурації [spring-config-02.xml] і, відповідно, реалізації шарів [Ui2, Metier2, Dao2]. Результати, отримані в консолі Eclipse:

ui(10,20)=-10

15.4. Conclusion

Створений нами додаток має велику гнучкість у розвитку. Реалізацію одного шару можна змінити за допомогою простої конфігурації. Код інших шарів залишається незмінним. Це досягається завдяки концепції IoC, яка є одним із двох стовпів Spring. Іншим стовпом є AOP (аспектно-орієнтоване програмування), яке ми не розглядали. Воно дозволяє, також за допомогою конфігурації, додавати «поведінку» до методу класу, не змінюючи його код.