JavaRush /Java блог /Random UA /Spring для лінивих. Основи, базові концепції та приклади ...
Стас Пасинков
26 рівень
Киев

Spring для лінивих. Основи, базові концепції та приклади з кодом. Частина 2

Стаття з групи Random UA
Минулої статті я двома словами пояснив, що таке спринг, що таке біни і контекст. Тепер настав час спробувати, як це все працює. Spring для лінивих.  Основи, базові концепції та приклади з кодом.  Частина 2 - 1Я буду робити в 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. Трохи докладніше я описав цей процес у цій статті (див. розділ " Підключення залежностей у мавені "). Тоді мавен сам знайде і скачає потрібні залежності, і в результаті у вас має вийти щось на кшталт такого:
Spring для лінивих.  Основи, базові концепції та приклади з кодом.  Частина 2 - 2
У лівому віконці видно структуру проекту з пакетом та класом 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;
  • якщо хочемо підключити можливість автоматичного сканування під час використання java-конфігурації - використовуємо інструкцію @ComponentScan.
Якщо нічого не зрозуміло — спробуйте прочитати цю статтю через пару днів. Ну або якщо ви на ранніх рівнях джаварашу, то можливо, що спринг для вас поки що трохи рано вивчати. Ви завжди зможете повернутися до цієї статті трохи пізніше, коли почуватиметеся вже впевненіше у програмуванні на java. Якщо все зрозуміло - можете спробувати перевести якийсь свій pet-проект на спринг :) Якщо щось зрозуміло, а щось не дуже - прошу в коментарі :) Туди ж і пропозиції та зауваження, якщо я десь ступив чи написав якусь дурість) У наступній статті ми різко пірнемо в spring-web-mvc і зробимо просте веб-додаток, використовуючи спринг.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ