Які scope існують в Spring
У Spring Framework області видимості (scopes) поділяються на базові (доступні в будь-якому типі застосунку) та веб-орієнтовані (доступні лише за наявності веб-контексту, наприкл...
🟢 Junior Level
У Spring Framework області видимості (scopes) поділяються на базові (доступні в будь-якому типі застосунку) та веб-орієнтовані (доступні лише за наявності веб-контексту, наприклад WebApplicationContext у Spring MVC / Boot Web).
Зведена таблиця всіх стандартних областей видимості
| Scope | Контекст | Кількість екземплярів і час життя |
|---|---|---|
singleton (за замовчуванням) |
Будь-який | Один екземпляр на весь ApplicationContext. Живе до закриття контексту. |
prototype |
Будь-який | Новий екземпляр під час кожного впровадження або виклику getBean(). |
request |
Web | Один екземпляр на один HTTP-запит. Знищується після завершення запиту. |
session |
Web | Один екземпляр на одну HTTP-сесію. Живе, поки активна сесія користувача. |
application |
Web | Один екземпляр на весь ServletContext. Живе на рівні контейнера сервлетів. |
websocket |
Web | Один екземпляр на життєвий цикл WebSocket-сесії. |
Синтаксис оголошення в Spring Boot
У сучасному Spring Boot для веб-скоупів введено зручні композитні анотації:
package com.example.scopes;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.ApplicationScope;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;
// 1. Singleton (за замовчуванням)
@Component
public class AppCatalogService { }
// 2. Prototype
@Component
@Scope("prototype")
public class HeavyReportTask { }
// 3. Request Scope (еквівалентно @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS))
@Component
@RequestScope
public class RequestAuditLog { }
// 4. Session Scope (еквівалентно @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS))
@Component
@SessionScope
public class UserShoppingCart { }
// 5. Application Scope (еквівалентно @Scope(value = "application", proxyMode = ScopedProxyMode.TARGET_CLASS))
@Component
@ApplicationScope
public class ServletContextMetadata { }
🟡 Middle Level
Особливості веб-скоупів і де вони зберігаються
Для функціонування веб-скоупів Spring використовує інтерфейс RequestAttributes та статичний диспетчер потоків RequestContextHolder, який зберігає контекст поточного запиту в ThreadLocal.
requestscope:- Екземпляр біна зберігається як атрибут в об’єкті сервлета:
HttpServletRequest.setAttribute(name, bean). - Після завершення обробки HTTP-запиту потік сервлета очищає атрибути, і бін знищується.
- Екземпляр біна зберігається як атрибут в об’єкті сервлета:
sessionscope:- Екземпляр зберігається в
HttpSession:HttpSession.setAttribute(name, bean). - Дозволяє ізолювати користувацький стан (кошик покупок, параметри локалі) між різними HTTP-запитами одного клієнта.
- Екземпляр зберігається в
applicationscope vssingleton:- У чому різниця?
Singletonіснує в єдиному екземплярі в межах одногоApplicationContext. Applicationscope створюється в єдиному екземплярі в межах усьогоServletContext.- Якщо в одному сервлет-контейнері (наприклад, Tomcat) розгорнуто кілька диспетчерів сервлетів (
DispatcherServlet), у кожного з них буде свій дочірнійApplicationContextзі своїми синглтонами, але бін із областю@ApplicationScopeбуде спільним для них усіх!
- У чому різниця?
Режими проксіювання: ScopedProxyMode
Під час оголошення бінів із вузьким скоупом (request, session) критично важливо правильно налаштувати proxyMode:
ScopedProxyMode:
1. DEFAULT / NO:
Проксі НЕ створюється. При ін'єкції в Singleton виникне помилка або "заморожування" першого об'єкта.
2. TARGET_CLASS (за замовчуванням для @RequestScope / @SessionScope):
Створюється CGLIB-проксі (нащадок класу). Працює для класів без інтерфейсів.
3. INTERFACES:
Створюється стандартний JDK Dynamic Proxy. Вимагає, щоб бін реалізовував хоча б один інтерфейс.
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserProfileSession userProfileSession() {
return new UserProfileSession();
}
🔴 Senior Level
Спеціальні та розподілені області видимості
1. @RefreshScope у Spring Cloud
У мікросервісній архітектурі критично важливо оновлювати конфігурацію (токени, прапорці фічей) без перезапуску JVM:
package com.example.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@RefreshScope
@ConfigurationProperties(prefix = "payment.gateway")
public class PaymentGatewayProperties {
private String apiUrl;
private int timeout;
// гетери, сетери
}
Як влаштований @RefreshScope під капотом:
- Spring Cloud реєструє реалізацію
org.springframework.cloud.context.scope.refresh.RefreshScope. - Бін обгортається в CGLIB-проксі з інтерцептором
RefreshScope. - Усі екземпляри кешуються у внутрішній структурі
BeanLifecycleDecoratorCache. - При надходженні події
RefreshEvent(або виклику ендпоінта/actuator/refresh) кеш екземплярів очищається. - При наступному виклику будь-якого методу через CGLIB-проксі інтерцептор виявляє відсутність екземпляра в кеші та викликає
createBeanInstance(), перечитуючи оновленіEnvironment-властивості!
2. Проблема Session Scope у розподіленому середовищі (Horizontal Scaling)
Якщо сервіс масштабовано на кілька екземплярів (Kubernetes Pods / інстанси за Load Balancer):
- Збереження даних у стандартному
sessionscope прив’язане до оперативної пам’яті конкретної ноди Tomcat. - Якщо наступний запит користувача потрапляє на іншу ноду через балансувальник, сесія губиться (або вимагає налаштування Sticky Sessions на рівні балансувальника).
- Архітектурне рішення: використовувати Spring Session + Redis. Сесія серіалізується і зберігається в централізованому кеші Redis, що робить
session scopeповністю незалежним від конкретної ноди.
Вбудований SimpleThreadScope у Spring Core
Spring «із коробки» надає SimpleThreadScope, але навмисно не реєструє його за замовчуванням:
@Configuration
public class ThreadScopeConfiguration {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
// Реєстрація вбудованого SimpleThreadScope
configurer.addScope("thread", new SimpleThreadScope());
return configurer;
}
}
Небезпека
SimpleThreadScopeу пулах потоків: Потоки в сервлет-контейнерах таThreadPoolTaskExecutorперевикористовуються (ThreadLocalне очищається автоматично). Якщо потік завершив задачу і повернувся в пул, бін ізSimpleThreadScopeзалишиться прив’язаним до потоку. Наступна задача, яку виконуватиме цей самий потік, отримає чужий «брудний» контекст. Крім того,SimpleThreadScopeне підтримує зворотні виклики знищення (destruction callbacks)!
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь
У Spring існує 6 стандартних областей видимості:
- Базові:
singleton(один наApplicationContext, за замовчуванням) таprototype(новий під час кожного запиту).- Веб-орієнтовані:
request(на HTTP-запит),session(на сесію користувача),application(наServletContext) таwebsocket(на вебсокет-сесію).- В екосистемі Spring Cloud додається
@RefreshScopeдля гарячого перезавантаження конфігурації. Веб-скоупи впроваджуються в синглтони через Scoped Proxy (ScopedProxyMode.TARGET_CLASS), звертаючись доRequestContextHolder(ThreadLocal).
4 каверзних питання з відповідями
1. У чому різниця між singleton та application scope?
Відповідь: Singleton створюється в єдиному екземплярі в межах конкретного ApplicationContext. Application scope створюється в єдиному екземплярі в межах ServletContext контейнера сервлетів. Якщо в одному контейнері запущено кілька ApplicationContext (наприклад, різні дочірні контексти сервлетів), у кожного буде свій singleton, але один спільний application scoped бін.
2. Як працює анотація @RequestScope у Spring Boot під капотом?
Відповідь: @RequestScope — це мета-анотація, що поєднує @Scope("request") та proxyMode = ScopedProxyMode.TARGET_CLASS. Вона генерує CGLIB-проксі, який перехоплює виклики методів і витягує цільовий бін з атрибутів поточного HttpServletRequest через статичний диспетчер RequestContextHolder.
3. Що робить @RefreshScope у Spring Cloud?
Відповідь: Він створює проксі над біном конфігурації. При отриманні події оновлення налаштувань (через /actuator/refresh або Spring Cloud Bus) внутрішній кеш екземплярів скидається. При наступному зверненні до проксі цільовий об’єкт інстанціюється заново з новими властивостями з Environment.
4. Чому небезпечно використовувати SimpleThreadScope у веб-застосунках?
Відповідь: Веб-сервери використовують пули потоків (Worker Pools). SimpleThreadScope зберігає об’єкти в ThreadLocal. При поверненні потоку в пул його ThreadLocal не очищається, що веде до витоків пам’яті та перехресного забруднення даних між різними користувачами. Також SimpleThreadScope не виконує методи очищення (destruction callbacks).
Червоні прапорці (чого категорично не можна говорити)
- ❌ «Веб-скоупи працюють у звичайних консольних Java-застосунках» — без
WebApplicationContextтаRequestContextListenerвикинетьсяIllegalStateException: No thread-bound request found. - ❌ «
@RequestScopeстворює звичайний об’єкт без проксі» —@RequestScopeза замовчуванням вмикаєScopedProxyMode.TARGET_CLASS. - ❌ «У розподіленій системі можна спокійно зберігати стан у
sessionscope без Redis» — без розподіленого сховища запити на сусідні ноди втратять сесію. - ❌ «
@RefreshScopeперезапускає весь Spring Boot контекст» — він інвалідує лише позначені біни, не перезавантажуючи JVM і контекст.