І так... На попередньому занятті ми коротко розглянули теоретичну частину IoC та DI. Також налаштували конфігураційний файл pom.xml для нашого проекту. Сьогодні ж розпочинаємо створення основної частини програми. Спочатку покажу як створюється програма без IoC/DI. А потім вже безпосередньо створимо програму, яка самостійно впроваджує залежність. Тобто управління кодом переходить до рук фреймворку (звучить моторошно). Поки програмою керуємо ми Уявимо, що є якась компанія. І в компанії (поки що) є два відділи: відділ розробки Java (JavaDevelopment) та відділ найму (HiringDepartment). Нехай клас, що описує "Відділ розробки Java", має два методи: String getName() - повертає ім'я співробітника, String getJob() - повертає посаду співробітника. (Лістинг 1)
package org.example;
public class JavaDevelopment {
public String getName(){
return "Alexa";
}
public String getJob(){
return "Middle Java developer";
}
}
Нехай клас, що описує відділ найму, має конструктор на вхід, який приймає співробітника, і метод void displayInfo(), який виводить інформацію про співробітників. (Лістинг 2)
package org.example;
public class HiringDepartment {
private JavaDevelopment javaDevelopment;
public HiringDepartment(JavaDevelopment javaDevelopment) {
this.javaDevelopment = javaDevelopment;
}
public void displayInfo() {
System.out.println("Name: " + javaDevelopment.getName());
System.out.println("Job: " + javaDevelopment.getJob());
}
}
А також є Main - клас, який керує всіма відділами. (Лістинг 3)
package org.example;
public class Main {
public static void main(String ... args){
JavaDevelopment javaDevelopment = new JavaDevelopment();
HiringDepartment hiringDepartment = new HiringDepartment(javaDevelopment);
hiringDepartment.displayInfo();
}
}
Поки що стабільність. При запуску Main - класу отримуємо наступний результат:
Name: Alexa
Job: Middle Java developer
А тепер уявімо, що у компанії справи йдуть чудово. Тому вони вирішабо розширити сферу діяльності і відкрабо відділ Python - розробки. І тут виникає питання: А як описати цей відділ на програмному рівні? Відповідь: треба "копи - пасти" скрізь, де потрібно описати цей відділ (старий, добрий метод 🙃). Для початку створимо саму клас, який би описав відділ "Пітоністів". (Лістинг 4)
package org.example;
public class PythonDevelopment {
public String getName(){
return "Mike";
}
public String getJob(){
return "Middle Python developer";
}
}
А потім передамо його на відділ найму (HiringDepartment). А в HiringDepartment'e про цей відділ нічого не сказано. Тому доведеться створити новий об'єкт класу PythonDevelopment і конструктор, який приймає Python - розробників. А також доведеться змінити метод displayInfo() - щоб він коректно відображав інформацію. (Лістинг 5)
package org.example;
public class HiringDepartment {
private JavaDevelopment javaDevelopment;
public HiringDepartment(JavaDevelopment javaDevelopment) {
this.javaDevelopment = javaDevelopment;
}
//Тут создается отдел найма для Python - разработчиков
private PythonDevelopment pythonDevelopment;
public HiringDepartment(PythonDevelopment pythonDevelopment) {
this.pythonDevelopment = pythonDevelopment;
}
//Тогда придется изменить метод displayInfo()
public void displayInfo() {
if(javaDevelopment != null) {
System.out.println("Name: " + javaDevelopment.getName());
System.out.println("Job: " + javaDevelopment.getJob());
} else if (pythonDevelopment != null){
System.out.println("Name: " + pythonDevelopment.getName());
System.out.println("Job: " + pythonDevelopment.getJob());
}
}
}
Як бачимо обсяг коду збільшилося вдвічі, або навіть більше. При великому обсязі коду падає його читальність. І найгірше ми всі об'єкти створюємо вручну і робимо класи, що сильно залежать один від одного. Ок, ми погодабось із цим. Лише описали один відділ. Ми цього нічого не втратимо. Ну якщо додасться ще один відділ? А що коли два? Три? А "копи - пастити" ніхто не забороняв. Так, "Копі – пастити" ніхто не забороняв, але це не професійно. Тиж програміст. І тут можна скористатися DI. Тобто працюватимемо не на рівні класів, а на рівні інтерфейсів. Тепер стан наших об'єктів буде зберігатися в інтерфейсах. Таким чином, залежності між класами будуть мінімальні. Для цього спочатку створимо інтерфейс Development, який має два методи для опису співробітника.
package org.example;
public interface Development {
String getName();
String getJob();
}
Нехай тоді два класи JavaDevelopment та PythonDevelopment імплементуються (успадковується) від цього інтерфейсу та перевизначать методи String getName() та String getJob(). (Лістинг 7, 8)
package org.example;
public class JavaDevelopment implements Development {
@Override
public String getName(){
return "Alexa";
}
@Override
public String getJob(){
return "Middle Java developer";
}
}
package org.example;
public class PythonDevelopment implements Development {
@Override
public String getName(){
return "Mike";
}
@Override
public String getJob(){
return "Middle Python developer";
}
}
Тоді в класі HiringDepartment можна просто визначити об'єкт інтерфейсу типу Development і конструктор теж можна передати такий об'єкт. (Лістинг 9)
package org.example;
public class HiringDepartment {
private Development development; //Определяем интерфейс
//Конструктор принимает об'єкт интерфейса
public HiringDepartment(Development development){
this.development = development;
}
public void displayInfo(){
System.out.println("Name: " + development.getName());
System.out.println("Job: " + development.getJob());
}
}
Як бачимо обсяг коду зменшився. І, головне, залежності мінімізувалися. А як власне впроваджуються значення та залежності до цих об'єктів? Є три способи застосування залежностей:
- За допомогою конструктора
- За допомогою сетерів
- Autowiring (автоматичне зв'язування)
- За допомогою XML-файлів (Застарілий спосіб)
- За допомогою анотацій + XML – файлів (Сучасний спосіб)
- За допомогою Java - коду (Сучасний спосіб)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="javaDeveloper" class="org.example.JavaDevelopment"/>
<bean id="pythonDeveloper" class="org.example.PythonDevelopment"/>
<bean id="hiringDepartment" class="org.example.HiringDepartment">
<constructor-arg ref="javaDeveloper"/>
</bean>
</beans>
Тепер по порядку. Перші вісім рядків коду нам не цікаві, дефолтні. Їх можна легко скопіювати. Тег <bean> </bean> визначає Spring bean. Bean - це об'єкт, який створюється і керується Spring container'ом. Простими словами, Spring container сам створює новий об'єкт класу для нас (наприклад: JavaDevelopment javaDevelopment = new JavaDevelopment();). Усередині цього тега є атрибути id та class. id визначає назву біну. Тому id можна буде звертатися до об'єкта. Він еквівалентний назві об'єкта у Java класі. class- Визначає назву класу, до якого прив'язаний наш бін (об'єкт). Потрібно зазначати повний шлях до класу. Зверніть увагу на бін hiringDepartment. Усередині цього бина є ще один тег <constructor-arg ref="javaDeveloper"/>. Тут відбувається використання залежності (у разі використання з допомогою конструктора). <constructor-arg> - вказує Spring'у, що Spring container повинен шукати залежності в конструкторі класу, визначений в атрибуті бина. А з яким об'єктом потрібно зв'язати визначає атрибут ref всередині тега <constructor-arg>. ref - вказує на id бина, з яким потрібно зв'язуватися. Якщо в ref замість javaDeveloper вказуємо id pythonDeveloper, зв'язка відбувається з класом PythonDevelopmen. Тепер потрібно описати Main class. Він виглядатиме ось так:
package org.example;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String ... args){
//Определяем контекст файл в котором содержатся прописанные нами бины
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//Получем бины, которые были определены в файле applicationContext.xml
HiringDepartment hiringDepartment = context.getBean("hiringDepartment", HiringDepartment.class);
hiringDepartment.displayInfo();
context.close(); //Контекст всегда должен закрываться
}
}
Що тут?
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Цей рядок пов'язує Main клас із .xml файлом, в якому описані наші біни. Значення, що передається в конструктор, повинен збігатися з ім'ям .xml файлу. (У нашому випадку applicationContext.xml).
HiringDepartment hiringDepartment = context.getBean("hiringDepartment", HiringDepartment.class);
Вказує, що хочемо отримати бін (об'єкт) класу HiringDepartment. Перший аргумент вказує на id бина що ми написали у xml файлі. Другий аргумент вказує на клас, з яким хочемо зв'язатися. Цей процес називається рефлексією .
hiringDepartment.displayInfo();
context.close(); //Контекст всегда должен закрываться
Тут ми спокійно отримуємо метод класу HiringDepartment. Зауважте, що для отримання об'єктів ми не використовували ключове слово new, і ніде не визначабо залежні об'єкти типу JavaDevelopment або PythonDevelopment. Їх просто описали у контексті файлу applicationContext.xml. Також зверніть увагу на останній рядок. Перед завершенням роботи завжди маємо закрити контекст. В іншому випадку ресурси не звільняться, може статися витік пам'яті або некоректність під час роботи програми. Якщо є питання чи пропозиції пишіть у коментарях, обов'язково відповім. Дякую за увагу. Вихідний код за посиланням Мій GitHub Віз Зміст курсу Далі буде...
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ