Скоуп (Scope) у Spring — це правило, яке визначає життєвий цикл біна: коли створювати новий екземпляр об'єкта, як довго його зберігати і коли знищувати. Це схоже на правила керування ресурсами — деякі ресурси треба створювати один раз і повторно використовувати, а інші вимагають нового екземпляра при кожному зверненні.
Скоупи важливі, бо в різних частинах вашого додатку можуть бути різні вимоги до створення об'єктів. Наприклад:
- Для сервісів "без стану" (stateless) ми хочемо використовувати той самий екземпляр у всьому додатку.
- А для об'єктів, що зберігають унікальні дані для кожного HTTP-запиту, нам потрібен новий екземпляр для кожного запиту.
У реальному проєкті розуміння скоупів допомагає гнучко керувати ресурсами і покращувати продуктивність додатку.
Стандартні скоупи Spring
Давайте розглянемо основні скоупи Spring:
- Singleton (за замовчуванням)
- Prototype
- Request
- Session
- Application
Singleton: "Один для всіх"
Singleton — це скоуп за замовчуванням. Коли бін позначений цим скоупом (або взагалі не позначений), Spring створює тільки один екземпляр біна для всього ApplicationContext. Цей бін буде використовуватися повторно щоразу, коли його запитують.
@Component
public class SingletonExample {
public SingletonExample() {
System.out.println("Singleton бін створено");
}
}
Якщо ви додасте згаданий вище бін у дві різні частини додатку, наприклад, у два різні класи, Spring створить один екземпляр і буде передавати його скрізь. Ось приклад:
@Component
public class MyService {
private final SingletonExample singletonExample;
@Autowired
public MyService(SingletonExample singletonExample) {
this.singletonExample = singletonExample;
System.out.println("MyService отримав бін SingletonExample");
}
}
@Component
public class AnotherService {
private final SingletonExample singletonExample;
@Autowired
public AnotherService(SingletonExample singletonExample) {
this.singletonExample = singletonExample;
System.out.println("AnotherService отримав бін SingletonExample");
}
}
Результат:
Singleton бін створено
MyService отримав бін SingletonExample
AnotherService отримав бін SingletonExample
Prototype: "На кожен запит — новий бін"
Prototype — скоуп для випадків, коли вам потрібен новий екземпляр біна при кожному запиті. Це корисно, якщо бін містить стан, який змінюється в залежності від бізнес-логіки.
@Component
@Scope("prototype")
public class PrototypeExample {
public PrototypeExample() {
System.out.println("Prototype бін створено");
}
}
Якщо два різні компоненти запросять PrototypeExample, Spring створить два окремі екземпляри.
@Component
public class MyPrototypeService {
private final PrototypeExample prototypeExample;
@Autowired
public MyPrototypeService(PrototypeExample prototypeExample) {
this.prototypeExample = prototypeExample;
System.out.println("MyPrototypeService отримав бін PrototypeExample");
}
}
@Component
public class AnotherPrototypeService {
private final PrototypeExample prototypeExample;
@Autowired
public AnotherPrototypeService(PrototypeExample prototypeExample) {
this.prototypeExample = prototypeExample;
System.out.println("AnotherPrototypeService отримав бін PrototypeExample");
}
}
Результат:
Prototype бін створено
MyPrototypeService отримав бін PrototypeExample
Prototype бін створено
AnotherPrototypeService отримав бін PrototypeExample
Request: "Новий бін для кожного HTTP-запиту"
Цей скоуп використовується у веб-додатках. На кожен новий HTTP-запит створюється окремий екземпляр біна.
@Component
@Scope("request")
public class RequestScopedBean {
public RequestScopedBean() {
System.out.println("Request бін створено");
}
}
використовувати цей scope можна лише в додатку з підтримкою веб-контексту, наприклад, зі Spring MVC.
Session: "Один бін на сесію користувача"
Session-скоуп створює бін, унікальний для сесії користувача.
@Component
@Scope("session")
public class SessionScopedBean {
public SessionScopedBean() {
System.out.println("Session бін створено");
}
}
Application: "Один бін для всього контексту додатку"
Цей скоуп схожий на singleton, але унікальний для веб-додатків із кількома сервлетами. Бін створюється один раз і використовується у всіх сервлетах додатку.
Як налаштувати скоупи?
Скоупи можна налаштувати за допомогою анотації @Scope. Ось приклад, як вказати скоуп для бінів:
@Component
@Scope("prototype") // Вказуємо scope для цього біна
public class MyPrototypeBean {
// Логіка біна
}
Для веб-орієнтованих скоупів (request, session, application) ви можете використовувати аналогічний підхід.
Як правильно вибрати скоуп?
Singleton ідеально підійде для сервісів без стану. Наприклад, сервіс відправки email — він просто виконує дію і не зберігає дані.
Prototype потрібен, коли кожному користувачу потрібен свій екземпляр. Уявіть форму реєстрації — у кожного свої дані.
Request/Session допомагають працювати з веб-даними. Наприклад, кошик в інтернет-магазині живе в межах сесії користувача.
Увага: вибір скоупу впливає на пам'ять і швидкодію. Особливо уважно використовуйте prototype — кожен новий екземпляр займає пам'ять, і її потрібно вчасно звільняти.
Типові помилки при використанні скоупів
- Використання
prototype-бінів уsingleton-бінах: якщо ви додастеprototype-бін уsingleton-бін через DI, Spring створить лише один екземпляр
prototypeі прикріпить його доsingleton. Для виправлення можна використовувати інтерфейсObjectFactoryабо анотацію@Lookup.Спроба використовувати веб-орієнтовані скоупи у НЕ веб-додатку: у цьому випадку ви отримаєте помилку
No Scope Registered for ScopeName.
Тепер ви можете впевнено працювати зі скоупами в Spring Framework, використовуючи їх для створення гнучких та масштабованих додатків! У наступній лекції розберемо анотації @Configuration і @Bean, які допоможуть вам ще краще керувати конфігураціями та бін-фабриками.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ