Какие 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 каверзных вопроса с ответами
-
В чём разница между
singletonиapplicationscope? Ответ:Singletonсоздается в единственном экземпляре в рамках конкретногоApplicationContext.Applicationscope создается в единственном экземпляре в рамкахServletContextконтейнера сервлетов. Если в одном контейнере запущено несколькоApplicationContext(например, разные дочерние контексты сервлетов), у каждого будет свойsingleton, но один общийapplicationscoped бин. -
Как работает аннотация
@RequestScopeв Spring Boot под капотом? Ответ:@RequestScope— это мета-аннотация, объединяющая@Scope("request")иproxyMode = ScopedProxyMode.TARGET_CLASS. Она генерирует CGLIB-прокси, который перехватывает вызовы методов и извлекает целевой бин из атрибутов текущегоHttpServletRequestчерез статический диспетчерRequestContextHolder. -
Что делает
@RefreshScopeв Spring Cloud? Ответ: Он создает прокси над бином конфигурации. При получении события обновления настроек (через/actuator/refresh) внутренний кэш экземпляров сбрасывается. При следующем обращении к прокси целевой объект инстанцируется заново с новыми свойствами изEnvironment. -
Почему опасно использовать
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 и контекст.