Что такое scope Bean?
НЕ храните изменяемое состояние в Singleton бинах (например, кэш пользователя в поле сервиса). Это приведёт к race condition и утечкам памяти. Вместо этого:
🟢 Junior Level
Scope — определяет, сколько экземпляров бина создаёт Spring.
Основные scope:
| Scope | Сколько экземпляров |
|---|---|
| Singleton | Один на всё приложение (по умолчанию) |
| Prototype | Новый при каждом запросе |
@Service // Singleton по умолчанию
public class UserService { }
@Scope("prototype")
@Service
public class ReportGenerator { }
Пример:
// Singleton: всегда один и тот же
UserService s1 = context.getBean(UserService.class);
UserService s2 = context.getBean(UserService.class);
s1 == s2 // → true
// Prototype: каждый раз новый
ReportGenerator r1 = context.getBean(ReportGenerator.class);
ReportGenerator r2 = context.getBean(ReportGenerator.class);
r1 == r2 // → false
🟡 Middle Level
Singleton
// По умолчанию!
@Service
public class UserService { }
// Один экземпляр на ApplicationContext
// Все потоки используют один объект
// → Должен быть Stateless (без состояния)!
Prototype
@Scope("prototype")
@Service
public class ReportGenerator { }
// Новый экземпляр при КАЖДОМ getBean()
// Spring создаёт, но НЕ управляет уничтожением!
// → @PreDestroy НЕ вызывается
Web Scopes
@Scope("request") // Один экземпляр на HTTP запрос
@Scope("session") // Один экземпляр на HTTP сессию
@Scope("application") // Один на ServletContext
Prototype в Singleton проблема
// ❌ Prototype создастся ОДИН раз!
@Service
public class SingletonService {
@Autowired
private PrototypeBean prototype; // Застрял один экземпляр!
}
// ✅ ObjectProvider
@Service
public class SingletonService {
@Autowired
private ObjectProvider<PrototypeBean> provider;
public void process() {
PrototypeBean bean = provider.getObject(); // Новый каждый раз!
}
}
🔴 Senior Level
Когда НЕ использовать Singleton с изменяемым состоянием
НЕ храните изменяемое состояние в Singleton бинах (например, кэш пользователя в поле сервиса). Это приведёт к race condition и утечкам памяти. Вместо этого:
- Вынесите состояние во внешнее хранилище (Redis, БД)
- Используйте Request scope для данных запроса
- Используйте Prototype если нужен новый экземпляр
Scoped Proxy
// Решение проблемы Prototype/Request в Singleton
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences preferences() {
return new UserPreferences();
}
// Spring внедрит прокси в Singleton
// Прокси при каждом вызове обращается к RequestContextHolder (ThreadLocal), который хранит атрибуты текущего HTTP-запроса. Таким образом, даже singleton-бин, инжектирующий request-scoped бин, каждый раз получает актуальный объект для текущего запроса.
Under the hood: где хранятся бины
Singleton → DefaultSingletonBeanRegistry (ConcurrentHashMap)
Request → RequestContextHolder (ThreadLocal) — ScopedProxyMode.TARGET_CLASS создаёт CGLIB-прокси, который при каждом вызове обращается к актуальной области (request/session). RequestContextHolder хранит данные текущего HTTP-запроса в ThreadLocal.
Session → SessionAttributes
Prototype → Создаётся, не хранится
Custom Scope
// Свой scope!
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();
public Object get(String name, ObjectFactory<?> factory) {
return threadLocal.get().computeIfAbsent(name, k -> factory.getObject());
}
}
// Регистрация
context.getBeanFactory().registerScope("thread", new ThreadScope());
Production Experience
Реальный сценарий: Request бин в Singleton
// ❌ Singleton получил Request бин при старте
// → Все запросы используют данные первого пользователя!
// ✅ ScopedProxyMode.TARGET_CLASS
// → Прокси → RequestContextHolder → актуальный бин
Best Practices
- Singleton — по умолчанию (Stateless!)
- Prototype — редко. Если вам просто нужен новый объект без зависимостей Spring — используйте
new. Но если объект требует внедрения зависимостей — Prototype оправдан. - Request/Session — для web данных
- Scoped Proxy — для внедрения узких scope в широкие
- ObjectProvider — альтернатива Scoped Proxy. ObjectProvider vs ScopedProxy: ObjectProvider — явный вызов
getObject()в коде (более читаемо). ScopedProxy — прокси прозрачно подставляется (менее заметно, но удобнее). ObjectProvider предпочтительнее для явного контроля. - Избегайте состояния в Singleton
Резюме для Senior
- Singleton = один на приложение, Stateless
- Prototype = новый каждый раз, @PreDestroy НЕ работает
- Request/Session = web scope
- Scoped Proxy = прокси → RequestContextHolder
- ObjectProvider = явный запрос бина
- Custom Scope = свой lifecycle
🎯 Шпаргалка для интервью
Обязательно знать:
- Singleton (по умолчанию) — один экземпляр на ApplicationContext, должен быть Stateless
- Prototype — новый экземпляр при каждом запросе,
@PreDestroyНЕ вызывается, Spring не управляет уничтожением - Web scopes:
request(на HTTP-запрос),session(на HTTP-сессию),application(на ServletContext) - Проблема Prototype в Singleton: prototype создаётся ОДИН раз при внедрении — используйте
ObjectProvider.getObject() - Scoped Proxy (
ScopedProxyMode.TARGET_CLASS) — CGLIB-прокси обращается кRequestContextHolder(ThreadLocal) при каждом вызове - Избегайте изменяемого состояния в Singleton — race condition и утечки памяти
- Custom Scope — можно создать свой (например,
ThreadScopeчерезThreadLocal) - Singleton хранит бины в
DefaultSingletonBeanRegistry(ConcurrentHashMap), Request — вThreadLocal
Частые уточняющие вопросы:
- Как внедрить Prototype в Singleton?
ObjectProvider<PrototypeBean>.getObject()— новый каждый раз, или Scoped Proxy. - Что такое Scoped Proxy? Прокси, которое при каждом вызове обращается к актуальной области (request/session) через
RequestContextHolder. - Почему Singleton должен быть Stateless? Один объект используется всеми потоками — изменяемое состояние = race condition.
- Когда использовать Prototype вместо
new? Когда объект требует внедрения зависимостей от Spring.
Красные флаги (НЕ говорить):
- «Singleton можно хранить изменяемое состояние» (race condition и утечки памяти)
- «Prototype бины уничтожаются автоматически» (
@PreDestroyНЕ вызывается, чистите вручную) - «Request/Session scope работают без Scoped Proxy в Singleton» (будет один экземпляр на все запросы!)
- «ObjectProvider и Scoped Proxy — одно и то же» (ObjectProvider — явный
getObject(), ScopedProxy — прозрачная подстановка)
Связанные темы:
- [[6. Что такое Bean Lifecycle]]
- [[7. Какие этапы жизненного цикла Bean]]
- [[9. Что делают методы с аннотацией @PostConstruct и @PreDestroy]]
- [[4. Что такое Bean в Spring]]
- [[1. Что такое Dependency Injection]]