Минулої статті я двома словами пояснив, що таке спринг, що таке біни і контекст. Тепер настав час спробувати, як це все працює. Я буду робити в Intellij Idea Enterprise Edition. Але всі мої приклади повинні також працювати і в безкоштовній Intellij Idea Community Edition. Просто якщо побачите на скріншотах, що у мене є якесь вікно, якого немає у вас - не переживайте, для цього проекту це не критично :) Для початку створюємо порожній проект. Я показував як це зробити в статті (читати до слів " Настав час наш мавен-проект перетворити на web-проект. ", після цього там вже показується як зробити веб-проект, а цього нам зараз не потрібно) Створимо в папці src/main /javaякийсь пакет (у моєму випадку я його назвав "
У лівому віконці видно структуру проекту з пакетом та класом
Тепер розглянемо завдання веселіше. З поліморфізмом та інтерфейсами :) Створимо інтерфейс
com.codegym.info.fatfaggy.animals
", ви можете назвати як хочете, просто в потрібних місцях не забудьте на свою назву замінювати). І створимо клас Main
у якому зробимо метод
public static void main(String[] args) {
...
}
Після чого відкриємо файл pom.xml і додамо розділ dependencies
. Тепер йдемо до мавенівського репозиторію і шукаємо там spring context останньої стабільної версії, і вставляємо те, що отримали всередину розділу dependencies
. Трохи докладніше я описав цей процес у цій статті (див. розділ " Підключення залежностей у мавені "). Тоді мавен сам знайде і скачає потрібні залежності, і в результаті у вас має вийти щось на кшталт такого:
Main
. У середньому вікні показано, як у мене виглядає pom.xml. Я ще додав туди розділ properties , в якому вказав мавену, яка у мене версія джави використовується у вихідниках і в яку версію компілювати. Це просто, щоб ідея попередження мені не кидала при запуску, що використовується стара версія джави. Можете робити, можете ні) У правому вікні — видно що хоч ми і підключабо тільки spring context — він автоматично за собою підтягнув ще й core, beans, aop і expression. Можна було підключати окремо кожен модуль, прописуючи в пам'ятці для кожного залежність із явною вказівкою версії, але нас поки що влаштовує і такий варіант, як є зараз. Тепер створимо пакет entities
(сутності) і в ньому створимо 3 класи: Cat
,Dog
, Parrot
. Нехай кожна тварина матиме ім'я ( private String name
, можете захардкодувати туди якісь значення), і гетери/сетери публічні. Тепер переходимо в клас Main
і в методі main()
пишемо щось на кшталт такого:
public static void main(String[] args) {
// створюємо порожній спринговий контекст, який шукатиме свої біни за анотаціями у вказаному пакеті
ApplicationContext context =
new AnnotationConfigApplicationContext("com.codegym.info.fatfaggy.animals.entities");
Cat cat = context.getBean(Cat.class);
Dog dog = (Dog) context.getBean("dog");
Parrot parrot = context.getBean("parrot-kesha", Parrot.class);
System.out.println(cat.getName());
System.out.println(dog.getName());
System.out.println(parrot.getName());
}
Спочатку ми створюємо об'єкт контексту, і в конструкторі вказуємо йому ім'я пакета, яке треба сканувати на наявність у ньому бінів. Тобто спринг пройдеться цим пакетом і спробує знайти такі класи, які відзначені спеціальними анотаціями, що дають спрингу зрозуміти, що це — бін. Після чого він створює об'єкти цих класів і поміщає їх у контекст. Після чого ми отримуємо із цього контексту котика. Звертаючись до об'єкта контексту, ми просимо його дати нам бін (об'єкт), і вказуємо, якого класу об'єкт нам потрібен (тут, до речі, можна вказувати не лише класи, а й інтерфейси). Після чого нам спринг повертає об'єкт цього класу, який ми вже зберігаємо в змінну. Далі ми просимо спринг дістати нам бін, який називається "dog". Коли спринг створюватиме об'єкт класуDog
— то він дасть йому стандартне ім'я (якщо явно не вказано ім'я біну, що створюється), яке є назвою класу об'єкта, тільки з маленької літери. Тому оскільки клас у нас називається Dog
, то ім'я такого бина буде "dog". Якби у нас там був об'єкт BufferedReader
, то йому спринг дав би ім'я за замовчуванням "bufferedReader". І оскільки в даному випадку (у джави) немає точної впевненості якого саме класу буде такий об'єкт — то повертається просто якийсь Object
, який ми вже потім ручками кастимо до потрібного нам типуDog
. Варіант із явною вказівкою класу зручніший. Ну і в третьому випадку ми отримуємо бін за класом та на ім'я. Просто може бути така ситуація, що в контексті виявиться кілька бінів якогось одного класу, і для того, щоб вказати, який саме бін нам потрібен — вказуємо його ім'я. Оскільки ми тут теж явно вказали клас, то й кастити нам уже не доводиться. Важливо!Якщо виявиться так, що спринг знайде кілька бінів за тими вимогами, що ми йому вказали — він не зможе визначити, який саме бін нам дати і кине виняток. Тому намагайтеся вказувати йому максимально точно який бін вам потрібен, щоб не виникло таких ситуацій. Якщо спринг не знайде у себе в контексті взагалі жодного бина за вашими умовами, він теж кине виняток. Ну і далі ми просто виводимо імена наших тварин на екран, щоб переконатися, що це реально саме ті об'єкти, які нам потрібні. Але якщо ми запустимо програму зараз, то побачимо, що спринг лається, що не може знайти у себе в контексті потрібних нам тварин. Так сталося тому, що не створив ці біни. Як я вже казав, коли спринг сканує класи - він шукає "свої" спрингові анотації там. І якщо не знаходить — то й не сприймає такі класи, як ті, біни яких йому треба створити. Щоб пофіксувати це, досить просто в класах наших тварин додати анотацію@Component
перед класом.
@Component
public class Cat {
private String name = "Барсик";
...
}
Але це не все. Якщо нам треба явно вказати спрингу, то бін для цього класу повинен мати якесь певне ім'я — це ім'я можна вказати в дужках після анотації. Наприклад, щоб спринг дав потрібне нам ім'я " parrot-kesha
" біну папужки, яким ми в main
-е потім цього папужки будемо отримувати - треба зробити приблизно так:
@Component("parrot-kesha")
public class Parrot {
private String name = "Кеша";
...
}
У цьому вся суть автоматичної конфігурації . Ви пишете ваші класи, відзначаєте їх потрібними анотаціями, і вказуєте спрингу пакет з вашими класами, яким він йде, шукає анотації та створює об'єкти таких класів. До речі, спринг шукатиме не лише анотації @Component
, а й усі інші анотації, які успадковуються від цієї. Наприклад, @Controller
, @RestController
, @Service
, @Repository
та інші, з якими ми познайомимося в подальших статтях. Тепер спробуємо зробити те саме, але використовуючи java-конфігурацію . Для початку - видалимо інструкції@Component
із наших класів. Для ускладнення завдання уявімо, що це не наші самописні класи, які ми можемо легко модифікувати, додавати щось, у тому числі і анотації. А начебто ці класи лежать запакованими в якійсь бібліотеці. У такому разі ми не можемо ніяк ці класи правити, щоб вони були сприйняті спрингом. Але ж об'єкти цих класів нам потрібні! Тут нам знадобиться java-конфігурація для створення таких об'єктів. Для початку, створимо пакет наприклад configs
, а в ньому - звичайний джава клас наприклад MyConfig
і позначимо його анотацією@Configuration
@Configuration
public class MyConfig {
}
Тепер нам потрібно трохи підправити в методі main()
те, як ми створюємо контекст. Ми можемо або прямо вказати там наш клас зі зміною:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class);
Якщо у нас кілька різних класів, де ми виробляємо створення бінів і ми хочемо підключити одразу кілька з них — просто вказуємо їх через кому:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class, MyAnotherConfig.class);
Ну і якщо у нас їх дуже багато, і ми хочемо їх підключити відразу все — просто вказуємо назву пакета, в якому вони у нас лежать:
ApplicationContext context =
new AnnotationConfigApplicationContext("com.codegym.info.fatfaggy.animals.configs");
У такому випадку спринг пройдеться по цьому пакету і знайде всі класи, які позначені анотацією @Configuration
. Ну і на випадок, якщо у нас реально велика програма, де конфіги розбиті по різних пакетах — просто вказуємо назву пакетів із конфігами через кому:
ApplicationContext context =
new AnnotationConfigApplicationContext("com.codegym.info.fatfaggy.animals.database.configs",
"com.codegym.info.fatfaggy.animals.root.configs",
"com.codegym.info.fatfaggy.animals.web.configs");
Ну чи назва більш спільного для всіх них пакета:
ApplicationContext context =
new AnnotationConfigApplicationContext("com.codegym.info.fatfaggy.animals");
Можете в себе зробити як хочете, але мені здається, перший варіант, де вказується просто клас з конфігами, підійде нашій програмі найкраще. При створенні контексту спринг шукатиме ті класи, які позначені анотацією @Configuration
і створить об'єкти цих класів у себе. Після чого він спробує викликати методи у цих класах, які позначені анотацією @Bean
, що означає, що такі методи повертатимуть бины (об'єкти), що він помістить собі у контекст. Ну що ж, тепер створимо біни котика, собачки та папужки у нашому класі з java-конфігурацією. Робиться це досить просто:
@Bean
public Cat getCat() {
return new Cat();
}
Виходить, ми тут самі вручну створабо нашого котика і дали спрингу, а він уже помістив цей наш об'єкт до себе в контекст. Оскільки ми явно не вказували ім'я нашого біна, то спринг дасть бину таке ж ім'я, як і назва методу. У нашому випадку, бін кота матиме ім'я " getCat
". Але так як в main
-е ми все-одно отримуємо кота не по імені, а по класу - то в даному випадку нам ім'я цього бина не важливо. Аналогічно зробіть і бін із собачкою, але врахуйте, що спринг назве такий бін за назвою методу. Щоб явно задати ім'я нашому біну з папужкою просто вказуємо його ім'я в дужках після анотації @Bean
:
@Bean("parrot-kesha")
public Object weNeedMoreParrots() {
return new Parrot();
}
Як видно, тут я вказав тип значення, що повертається Object
, а метод назвав взагалі як завгодно. На назву бина це ніяк не впливає, тому що ми його явно тут задаємо. Але краще все-таки тип значення, що повертається, і ім'я методу вказувати не "зі стелі", а більш-менш зрозуміло. Просто навіть для самих себе, коли за рік відкриєте цей проект. :) Тепер розглянемо ситуацію, коли для створення одного біну нам потрібно використовувати інший бін . Наприклад, ми хочемо, щоб ім'я кота в біні кота складалося з імені папужки та рядка "-killer". Без проблем!
@Bean
public Cat getCat(Parrot parrot) {
Cat cat = new Cat();
cat.setName(parrot.getName() + "-killer");
return cat;
}
Тут спринг побачить, що перед тим, як створювати цей бін, йому доведеться сюди передати вже створений бін папужки. Тому він вибудує ланцюжок викликів наших методів так, щоб спочатку зголосився метод зі створення папужки, а потім вже передасть цього папужки в метод зі створення кота. Тут спрацювала та штука, яка називається dependency injection : спринг сам передав потрібний бін папужки в наш метод. Якщо ідея буде лаятися на змінну parrot
– не забудьте змінити тип значення, що повертається в методі зі створення папужки з Object
на Parrot
. Крім того, джава-конфігурування дозволяє виконувати абсолютно будь-який джава-коду методах зі створення бінів. Можна робити реально будь-що: створювати інші допоміжні об'єкти, викликати будь-які інші методи, навіть не помічені спринговими анотаціями, робити цикли, умови - що тільки на думку спаде! Цього всього за допомогою автоматичної конфігурації, і тим більше використанням xml-конфігів - не домогтися.
WeekDay
, і створимо 7 класів, які імплементували цей інтерфейс: Monday
, Tuesday
, Wednesday
, Thursday
, Friday
, Saturday
, Sunday
. Створимо в інтерфейсі метод String getWeekDayName()
, який повертав назву дня тижня відповідного класу. Тобто, клас Monday
повертав би " monday
", ітд. Допустимо, стоїть завдання при запуску нашої програми помістити в контекст такий бін, який би відповідав поточному дню тижня. Не всі біни всіх класів, які імплементують WeekDay
інтерфейс, лише потрібний нам. Це можна зробити приблизно так:
@Bean
public WeekDay getDay() {
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
switch (dayOfWeek) {
case MONDAY: return new Monday();
case TUESDAY: return new Tuesday();
case WEDNESDAY: return new Wednesday();
case THURSDAY: return new Thursday();
case FRIDAY: return new Friday();
case SATURDAY: return new Saturday();
default: return new Sunday();
}
}
Тут тип значення, що повертається - це наш інтерфейс, а повертаються шляхом реальні об'єкти класів-реалізацій інтерфейсу в залежності від поточного дня тижня. Тепер у методі main()
ми можемо зробити так:
WeekDay weekDay = context.getBean(WeekDay.class);
System.out.println("It's " + weekDay.getWeekDayName() + " today!");
Мені видало, що сьогодні неділя :) Впевнений, що якщо я запущу програму завтра, то в контексті виявиться зовсім інший об'єкт. Зверніть увагу, тут ми отримуємо бін просто за інтерфейсом: context.getBean(WeekDay.class)
. Спрінг подивиться у своєму контексті який із бінів у нього там імплементує такий інтерфейс — його й поверне. Ну а далі вже виходить, що у змінній типу WeekDay
виявився об'єкт типу Sunday
, і починається вже знайомий усім нам поліморфізм, при роботі із цією змінною. :) І кілька слів про комбінований підхід , де частина бінів створюється спрингом самостійно, використовуючи сканування пакетів на наявність класів з анотацією @Component
, а деякі інші біни – створюються вже використовуючи java-конфіг. Для цього повернемося до початкового варіанту, коли класи Cat
, Dog
іParrot
були відзначені інструкцією @Component
. Припустимо, ми хочемо створити біни наших тварин за допомогою автоматичного сканування пакету entities
спрингом, а ось бін з днем тижня створювати так, як ми тільки-но зробабо. Все що треба зробити - це додати на рівні класу MyConfig
, який ми вказуємо при створенні контексту в main
-е анотацію @ComponentScan
, і вказати в дужках пакет, який треба просканувати та створити біни потрібних класів автоматично:
@Configuration
@ComponentScan("com.codegym.info.fatfaggy.animals.entities")
public class MyConfig {
@Bean
public WeekDay getDay() {
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
switch (dayOfWeek) {
case MONDAY: return new Monday();
case TUESDAY: return new Tuesday();
case WEDNESDAY: return new Wednesday();
case THURSDAY: return new Thursday();
case FRIDAY: return new Friday();
case SATURDAY: return new Saturday();
default: return new Sunday();
}
}
}
Виходить, що при створенні контексту спринг бачить, що потрібно обробити клас MyConfig
. Заходить у нього і бачить, що потрібно просканувати пакет " com.codegym.info.fatfaggy.animals.entities
" і створити біни тих класів, після чого виконує метод getDay()
із класу MyConfig
і додає бін типу WeekDay
собі в контекст. У методі main()
ми тепер маємо доступ до всіх потрібних нам бінів: і до об'єктів тварин, і до бина з днем тижня. Як зробити так, щоб спринг підхопив ще й якісь xml-конфіги - нагугліть в інтернеті самостійно, якщо знадобиться :) Резюме:
- намагатися використовувати автоматичну конфігурацію;
- при автоматичної конфігурації вказуємо ім'я пакета, де лежать класи, біни яких треба створити;
- такі класи позначаються анотацією
@Component;
- спринг проходить за всіма такими класами і створює їх об'єкти і поміщає собі в контекст;
- якщо автоматична конфіграція нам з якихось причин не підходить – використовуємо java-конфігурування;
- у такому разі створюємо звичайний джава клас, методи якого повертатимуть потрібні нам об'єкти, і помічаємо такий клас анотацією
@Configuration
на випадок, якщо скануватимемо весь пакет повністю, а не вказувати конкретний клас із конфігурацією при створенні контексту; - методи цього класу, які повертають біни - позначаємо інструкцією
@Bean
;
@ComponentScan
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ